2025-11-28 23:30:49 +08:00
|
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"carrotskin/internal/model"
|
|
|
|
|
|
"carrotskin/internal/repository"
|
2025-12-02 10:33:19 +08:00
|
|
|
|
"carrotskin/pkg/auth"
|
2025-11-28 23:30:49 +08:00
|
|
|
|
"carrotskin/pkg/redis"
|
|
|
|
|
|
"carrotskin/pkg/utils"
|
|
|
|
|
|
"context"
|
2025-12-02 22:52:33 +08:00
|
|
|
|
"encoding/base64"
|
2025-11-28 23:30:49 +08:00
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"net"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
2025-11-30 18:56:56 +08:00
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
|
2025-11-28 23:30:49 +08:00
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// SessionKeyPrefix Redis会话键前缀
|
|
|
|
|
|
const SessionKeyPrefix = "Join_"
|
|
|
|
|
|
|
|
|
|
|
|
// SessionTTL 会话超时时间 - 增加到15分钟
|
|
|
|
|
|
const SessionTTL = 15 * time.Minute
|
|
|
|
|
|
|
|
|
|
|
|
type SessionData struct {
|
|
|
|
|
|
AccessToken string `json:"accessToken"`
|
|
|
|
|
|
UserName string `json:"userName"`
|
|
|
|
|
|
SelectedProfile string `json:"selectedProfile"`
|
|
|
|
|
|
IP string `json:"ip"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
// yggdrasilService YggdrasilService的实现
|
|
|
|
|
|
type yggdrasilService struct {
|
|
|
|
|
|
db *gorm.DB
|
|
|
|
|
|
userRepo repository.UserRepository
|
|
|
|
|
|
profileRepo repository.ProfileRepository
|
|
|
|
|
|
textureRepo repository.TextureRepository
|
|
|
|
|
|
tokenRepo repository.TokenRepository
|
|
|
|
|
|
yggdrasilRepo repository.YggdrasilRepository
|
|
|
|
|
|
signatureService *signatureService
|
|
|
|
|
|
redis *redis.Client
|
|
|
|
|
|
logger *zap.Logger
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewYggdrasilService 创建YggdrasilService实例
|
|
|
|
|
|
func NewYggdrasilService(
|
|
|
|
|
|
db *gorm.DB,
|
|
|
|
|
|
userRepo repository.UserRepository,
|
|
|
|
|
|
profileRepo repository.ProfileRepository,
|
|
|
|
|
|
textureRepo repository.TextureRepository,
|
|
|
|
|
|
tokenRepo repository.TokenRepository,
|
|
|
|
|
|
yggdrasilRepo repository.YggdrasilRepository,
|
|
|
|
|
|
signatureService *signatureService,
|
|
|
|
|
|
redisClient *redis.Client,
|
|
|
|
|
|
logger *zap.Logger,
|
|
|
|
|
|
) YggdrasilService {
|
|
|
|
|
|
return &yggdrasilService{
|
|
|
|
|
|
db: db,
|
|
|
|
|
|
userRepo: userRepo,
|
|
|
|
|
|
profileRepo: profileRepo,
|
|
|
|
|
|
textureRepo: textureRepo,
|
|
|
|
|
|
tokenRepo: tokenRepo,
|
|
|
|
|
|
yggdrasilRepo: yggdrasilRepo,
|
|
|
|
|
|
signatureService: signatureService,
|
|
|
|
|
|
redis: redisClient,
|
|
|
|
|
|
logger: logger,
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
func (s *yggdrasilService) GetUserIDByEmail(ctx context.Context, email string) (int64, error) {
|
|
|
|
|
|
user, err := s.userRepo.FindByEmail(email)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
if err != nil {
|
2025-12-02 22:52:33 +08:00
|
|
|
|
return 0, errors.New("用户不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
if user == nil {
|
|
|
|
|
|
return 0, errors.New("用户不存在")
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
2025-12-02 22:52:33 +08:00
|
|
|
|
return user.ID, nil
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
func (s *yggdrasilService) VerifyPassword(ctx context.Context, password string, userID int64) error {
|
|
|
|
|
|
passwordStore, err := s.yggdrasilRepo.GetPasswordByID(userID)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return errors.New("未生成密码")
|
|
|
|
|
|
}
|
2025-12-02 10:33:19 +08:00
|
|
|
|
// 使用 bcrypt 验证密码
|
|
|
|
|
|
if !auth.CheckPassword(passwordStore, password) {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return errors.New("密码错误")
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
func (s *yggdrasilService) ResetYggdrasilPassword(ctx context.Context, userID int64) (string, error) {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
// 生成新的16位随机密码(明文,返回给用户)
|
|
|
|
|
|
plainPassword := model.GenerateRandomPassword(16)
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 bcrypt 加密密码后存储
|
|
|
|
|
|
hashedPassword, err := auth.HashPassword(plainPassword)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", fmt.Errorf("密码加密失败: %w", err)
|
|
|
|
|
|
}
|
2025-11-30 18:56:56 +08:00
|
|
|
|
|
|
|
|
|
|
// 检查Yggdrasil记录是否存在
|
2025-12-02 22:52:33 +08:00
|
|
|
|
_, err = s.yggdrasilRepo.GetPasswordByID(userID)
|
2025-11-30 18:56:56 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 如果不存在,创建新记录
|
|
|
|
|
|
yggdrasil := model.Yggdrasil{
|
2025-12-02 22:52:33 +08:00
|
|
|
|
ID: userID,
|
2025-12-02 10:33:19 +08:00
|
|
|
|
Password: hashedPassword,
|
2025-11-30 18:56:56 +08:00
|
|
|
|
}
|
2025-12-02 22:52:33 +08:00
|
|
|
|
if err := s.db.Create(&yggdrasil).Error; err != nil {
|
2025-11-30 18:56:56 +08:00
|
|
|
|
return "", fmt.Errorf("创建Yggdrasil密码失败: %w", err)
|
|
|
|
|
|
}
|
2025-12-02 10:33:19 +08:00
|
|
|
|
return plainPassword, nil
|
2025-11-30 18:56:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
// 如果存在,更新密码(存储加密后的密码)
|
2025-12-02 22:52:33 +08:00
|
|
|
|
if err := s.yggdrasilRepo.ResetPassword(userID, hashedPassword); err != nil {
|
2025-11-30 18:56:56 +08:00
|
|
|
|
return "", fmt.Errorf("重置Yggdrasil密码失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
|
// 返回明文密码给用户
|
|
|
|
|
|
return plainPassword, nil
|
2025-11-30 18:56:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
func (s *yggdrasilService) JoinServer(ctx context.Context, serverID, accessToken, selectedProfile, ip string) error {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
// 输入验证
|
2025-12-02 22:52:33 +08:00
|
|
|
|
if serverID == "" || accessToken == "" || selectedProfile == "" {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return errors.New("参数不能为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证serverId格式,防止注入攻击
|
2025-12-02 22:52:33 +08:00
|
|
|
|
if len(serverID) > 100 || strings.ContainsAny(serverID, "<>\"'&") {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return errors.New("服务器ID格式无效")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证IP格式
|
|
|
|
|
|
if ip != "" {
|
|
|
|
|
|
if net.ParseIP(ip) == nil {
|
|
|
|
|
|
return errors.New("IP地址格式无效")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取和验证Token
|
2025-12-02 22:52:33 +08:00
|
|
|
|
token, err := s.tokenRepo.FindByAccessToken(accessToken)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
if err != nil {
|
2025-12-02 22:52:33 +08:00
|
|
|
|
s.logger.Error(
|
2025-11-28 23:30:49 +08:00
|
|
|
|
"验证Token失败",
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
zap.String("accessToken", accessToken),
|
|
|
|
|
|
)
|
|
|
|
|
|
return fmt.Errorf("验证Token失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化UUID并验证与Token关联的配置文件
|
|
|
|
|
|
formattedProfile := utils.FormatUUID(selectedProfile)
|
|
|
|
|
|
if token.ProfileId != formattedProfile {
|
|
|
|
|
|
return errors.New("selectedProfile与Token不匹配")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
profile, err := s.profileRepo.FindByUUID(formattedProfile)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
if err != nil {
|
2025-12-02 22:52:33 +08:00
|
|
|
|
s.logger.Error(
|
2025-11-28 23:30:49 +08:00
|
|
|
|
"获取Profile失败",
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
zap.String("uuid", formattedProfile),
|
|
|
|
|
|
)
|
|
|
|
|
|
return fmt.Errorf("获取Profile失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建会话数据
|
|
|
|
|
|
data := SessionData{
|
|
|
|
|
|
AccessToken: accessToken,
|
|
|
|
|
|
UserName: profile.Name,
|
|
|
|
|
|
SelectedProfile: formattedProfile,
|
|
|
|
|
|
IP: ip,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 序列化会话数据
|
|
|
|
|
|
marshaledData, err := json.Marshal(data)
|
|
|
|
|
|
if err != nil {
|
2025-12-02 22:52:33 +08:00
|
|
|
|
s.logger.Error(
|
2025-11-28 23:30:49 +08:00
|
|
|
|
"[ERROR]序列化会话数据失败",
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
return fmt.Errorf("序列化会话数据失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
// 存储会话数据到Redis - 使用传入的 ctx
|
|
|
|
|
|
sessionKey := SessionKeyPrefix + serverID
|
|
|
|
|
|
if err = s.redis.Set(ctx, sessionKey, marshaledData, SessionTTL); err != nil {
|
|
|
|
|
|
s.logger.Error(
|
2025-11-28 23:30:49 +08:00
|
|
|
|
"保存会话数据失败",
|
|
|
|
|
|
zap.Error(err),
|
2025-12-02 22:52:33 +08:00
|
|
|
|
zap.String("serverId", serverID),
|
2025-11-28 23:30:49 +08:00
|
|
|
|
)
|
|
|
|
|
|
return fmt.Errorf("保存会话数据失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
s.logger.Info(
|
2025-11-28 23:30:49 +08:00
|
|
|
|
"玩家成功加入服务器",
|
|
|
|
|
|
zap.String("username", profile.Name),
|
2025-12-02 22:52:33 +08:00
|
|
|
|
zap.String("serverId", serverID),
|
2025-11-28 23:30:49 +08:00
|
|
|
|
)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
func (s *yggdrasilService) HasJoinedServer(ctx context.Context, serverID, username, ip string) error {
|
|
|
|
|
|
if serverID == "" || username == "" {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return errors.New("服务器ID和用户名不能为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
// 从Redis获取会话数据 - 使用传入的 ctx
|
|
|
|
|
|
sessionKey := SessionKeyPrefix + serverID
|
|
|
|
|
|
data, err := s.redis.GetBytes(ctx, sessionKey)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
if err != nil {
|
2025-12-02 22:52:33 +08:00
|
|
|
|
s.logger.Error("[ERROR] 获取会话数据失败:", zap.Error(err), zap.Any("serverId:", serverID))
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return fmt.Errorf("获取会话数据失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 反序列化会话数据
|
|
|
|
|
|
var sessionData SessionData
|
|
|
|
|
|
if err = json.Unmarshal(data, &sessionData); err != nil {
|
2025-12-02 22:52:33 +08:00
|
|
|
|
s.logger.Error("[ERROR] 解析会话数据失败: ", zap.Error(err))
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return fmt.Errorf("解析会话数据失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证用户名
|
|
|
|
|
|
if sessionData.UserName != username {
|
|
|
|
|
|
return errors.New("用户名不匹配")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证IP(如果提供)
|
|
|
|
|
|
if ip != "" && sessionData.IP != ip {
|
|
|
|
|
|
return errors.New("IP地址不匹配")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2025-12-02 22:52:33 +08:00
|
|
|
|
|
|
|
|
|
|
func (s *yggdrasilService) SerializeProfile(ctx context.Context, profile model.Profile) map[string]interface{} {
|
|
|
|
|
|
// 创建基本材质数据
|
|
|
|
|
|
texturesMap := make(map[string]interface{})
|
|
|
|
|
|
textures := map[string]interface{}{
|
|
|
|
|
|
"timestamp": time.Now().UnixMilli(),
|
|
|
|
|
|
"profileId": profile.UUID,
|
|
|
|
|
|
"profileName": profile.Name,
|
|
|
|
|
|
"textures": texturesMap,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理皮肤
|
|
|
|
|
|
if profile.SkinID != nil {
|
|
|
|
|
|
skin, err := s.textureRepo.FindByID(*profile.SkinID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("[ERROR] 获取皮肤失败:", zap.Error(err), zap.Any("SkinID:", *profile.SkinID))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
texturesMap["SKIN"] = map[string]interface{}{
|
|
|
|
|
|
"url": skin.URL,
|
|
|
|
|
|
"metadata": skin.Size,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理披风
|
|
|
|
|
|
if profile.CapeID != nil {
|
|
|
|
|
|
cape, err := s.textureRepo.FindByID(*profile.CapeID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("[ERROR] 获取披风失败:", zap.Error(err), zap.Any("capeID:", *profile.CapeID))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
texturesMap["CAPE"] = map[string]interface{}{
|
|
|
|
|
|
"url": cape.URL,
|
|
|
|
|
|
"metadata": cape.Size,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将textures编码为base64
|
|
|
|
|
|
bytes, err := json.Marshal(textures)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("[ERROR] 序列化textures失败: ", zap.Error(err))
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
textureData := base64.StdEncoding.EncodeToString(bytes)
|
|
|
|
|
|
signature, err := s.signatureService.SignStringWithSHA1withRSA(textureData)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("[ERROR] 签名textures失败: ", zap.Error(err))
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建结果
|
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
|
"id": profile.UUID,
|
|
|
|
|
|
"name": profile.Name,
|
|
|
|
|
|
"properties": []Property{
|
|
|
|
|
|
{
|
|
|
|
|
|
Name: "textures",
|
|
|
|
|
|
Value: textureData,
|
|
|
|
|
|
Signature: signature,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
return data
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *yggdrasilService) SerializeUser(ctx context.Context, user *model.User, uuid string) map[string]interface{} {
|
|
|
|
|
|
if user == nil {
|
|
|
|
|
|
s.logger.Error("[ERROR] 尝试序列化空用户")
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
|
"id": uuid,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 正确处理 *datatypes.JSON 指针类型
|
|
|
|
|
|
// 如果 Properties 为 nil,则设置为 nil;否则解引用并解析为 JSON 值
|
|
|
|
|
|
if user.Properties == nil {
|
|
|
|
|
|
data["properties"] = nil
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// datatypes.JSON 是 []byte 类型,需要解析为实际的 JSON 值
|
|
|
|
|
|
var propertiesValue interface{}
|
|
|
|
|
|
if err := json.Unmarshal(*user.Properties, &propertiesValue); err != nil {
|
|
|
|
|
|
s.logger.Warn("[WARN] 解析用户Properties失败,使用空值", zap.Error(err))
|
|
|
|
|
|
data["properties"] = nil
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data["properties"] = propertiesValue
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *yggdrasilService) GeneratePlayerCertificate(ctx context.Context, uuid string) (map[string]interface{}, error) {
|
|
|
|
|
|
if uuid == "" {
|
|
|
|
|
|
return nil, fmt.Errorf("UUID不能为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
s.logger.Info("[INFO] 开始生成玩家证书,用户UUID: %s", zap.String("uuid", uuid))
|
|
|
|
|
|
|
|
|
|
|
|
keyPair, err := s.profileRepo.GetKeyPair(uuid)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Info("[INFO] 获取用户密钥对失败,将创建新密钥对: %v",
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
zap.String("uuid", uuid),
|
|
|
|
|
|
)
|
|
|
|
|
|
keyPair = nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有找到密钥对或密钥对已过期,创建一个新的
|
|
|
|
|
|
now := time.Now().UTC()
|
|
|
|
|
|
if keyPair == nil || keyPair.Refresh.Before(now) || keyPair.PrivateKey == "" || keyPair.PublicKey == "" {
|
|
|
|
|
|
s.logger.Info("[INFO] 为用户创建新的密钥对: %s", zap.String("uuid", uuid))
|
|
|
|
|
|
keyPair, err = s.signatureService.NewKeyPair()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("[ERROR] 生成玩家证书密钥对失败: %v",
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
zap.String("uuid", uuid),
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil, fmt.Errorf("生成玩家证书密钥对失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 保存密钥对到数据库
|
|
|
|
|
|
err = s.profileRepo.UpdateKeyPair(uuid, keyPair)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Warn("[WARN] 更新用户密钥对失败: %v",
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
zap.String("uuid", uuid),
|
|
|
|
|
|
)
|
|
|
|
|
|
// 继续执行,即使保存失败
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算expiresAt的毫秒时间戳
|
|
|
|
|
|
expiresAtMillis := keyPair.Expiration.UnixMilli()
|
|
|
|
|
|
|
|
|
|
|
|
// 返回玩家证书
|
|
|
|
|
|
certificate := map[string]interface{}{
|
|
|
|
|
|
"keyPair": map[string]interface{}{
|
|
|
|
|
|
"privateKey": keyPair.PrivateKey,
|
|
|
|
|
|
"publicKey": keyPair.PublicKey,
|
|
|
|
|
|
},
|
|
|
|
|
|
"publicKeySignature": keyPair.PublicKeySignature,
|
|
|
|
|
|
"publicKeySignatureV2": keyPair.PublicKeySignatureV2,
|
|
|
|
|
|
"expiresAt": expiresAtMillis,
|
|
|
|
|
|
"refreshedAfter": keyPair.Refresh.UnixMilli(),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("[INFO] 成功生成玩家证书", zap.String("uuid", uuid))
|
|
|
|
|
|
return certificate, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *yggdrasilService) GetPublicKey(ctx context.Context) (string, error) {
|
|
|
|
|
|
return s.signatureService.GetPublicKeyFromRedis()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type Property struct {
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
Value string `json:"value"`
|
|
|
|
|
|
Signature string `json:"signature,omitempty"`
|
|
|
|
|
|
}
|