661 lines
21 KiB
Go
661 lines
21 KiB
Go
package handler
|
||
|
||
import (
|
||
"bytes"
|
||
"carrotskin/internal/model"
|
||
"carrotskin/internal/service"
|
||
"carrotskin/pkg/database"
|
||
"carrotskin/pkg/logger"
|
||
"carrotskin/pkg/redis"
|
||
"carrotskin/pkg/utils"
|
||
"io"
|
||
"net/http"
|
||
"regexp"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// 常量定义
|
||
const (
|
||
ErrInternalServer = "服务器内部错误"
|
||
// 错误类型
|
||
ErrInvalidEmailFormat = "邮箱格式不正确"
|
||
ErrInvalidPassword = "密码必须至少包含8个字符,只能包含字母、数字和特殊字符"
|
||
ErrWrongPassword = "密码错误"
|
||
ErrUserNotMatch = "用户不匹配"
|
||
|
||
// 错误消息
|
||
ErrInvalidRequest = "请求格式无效"
|
||
ErrJoinServerFailed = "加入服务器失败"
|
||
ErrServerIDRequired = "服务器ID不能为空"
|
||
ErrUsernameRequired = "用户名不能为空"
|
||
ErrSessionVerifyFailed = "会话验证失败"
|
||
ErrProfileNotFound = "未找到用户配置文件"
|
||
ErrInvalidParams = "无效的请求参数"
|
||
ErrEmptyUserID = "用户ID为空"
|
||
ErrUnauthorized = "无权操作此配置文件"
|
||
ErrGetProfileService = "获取配置文件服务失败"
|
||
|
||
// 成功信息
|
||
SuccessProfileCreated = "创建成功"
|
||
MsgRegisterSuccess = "注册成功"
|
||
|
||
// 错误消息
|
||
ErrGetProfile = "获取配置文件失败"
|
||
ErrGetTextureService = "获取材质服务失败"
|
||
ErrInvalidContentType = "无效的请求内容类型"
|
||
ErrParseMultipartForm = "解析多部分表单失败"
|
||
ErrGetFileFromForm = "从表单获取文件失败"
|
||
ErrInvalidFileType = "无效的文件类型,仅支持PNG图片"
|
||
ErrSaveTexture = "保存材质失败"
|
||
ErrSetTexture = "设置材质失败"
|
||
ErrGetTexture = "获取材质失败"
|
||
|
||
// 内存限制
|
||
MaxMultipartMemory = 32 << 20 // 32 MB
|
||
|
||
// 材质类型
|
||
TextureTypeSkin = "SKIN"
|
||
TextureTypeCape = "CAPE"
|
||
|
||
// 内容类型
|
||
ContentTypePNG = "image/png"
|
||
ContentTypeMultipart = "multipart/form-data"
|
||
|
||
// 表单参数
|
||
FormKeyModel = "model"
|
||
FormKeyFile = "file"
|
||
|
||
// 元数据键
|
||
MetaKeyModel = "model"
|
||
)
|
||
|
||
// 正则表达式
|
||
var (
|
||
// 邮箱正则表达式
|
||
emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
|
||
|
||
// 密码强度正则表达式(最少8位,只允许字母、数字和特定特殊字符)
|
||
passwordRegex = regexp.MustCompile(`^[a-zA-Z0-9!@#$%^&*]{8,}$`)
|
||
)
|
||
|
||
// 请求结构体
|
||
type (
|
||
// AuthenticateRequest 认证请求
|
||
AuthenticateRequest struct {
|
||
Agent map[string]interface{} `json:"agent"`
|
||
ClientToken string `json:"clientToken"`
|
||
Identifier string `json:"username" binding:"required"`
|
||
Password string `json:"password" binding:"required"`
|
||
RequestUser bool `json:"requestUser"`
|
||
}
|
||
|
||
// ValidTokenRequest 验证令牌请求
|
||
ValidTokenRequest struct {
|
||
AccessToken string `json:"accessToken" binding:"required"`
|
||
ClientToken string `json:"clientToken"`
|
||
}
|
||
|
||
// RefreshRequest 刷新令牌请求
|
||
RefreshRequest struct {
|
||
AccessToken string `json:"accessToken" binding:"required"`
|
||
ClientToken string `json:"clientToken"`
|
||
RequestUser bool `json:"requestUser"`
|
||
SelectedProfile map[string]interface{} `json:"selectedProfile"`
|
||
}
|
||
|
||
// SignOutRequest 登出请求
|
||
SignOutRequest struct {
|
||
Email string `json:"username" binding:"required"`
|
||
Password string `json:"password" binding:"required"`
|
||
}
|
||
|
||
JoinServerRequest struct {
|
||
ServerID string `json:"serverId" binding:"required"`
|
||
AccessToken string `json:"accessToken" binding:"required"`
|
||
SelectedProfile string `json:"selectedProfile" binding:"required"`
|
||
}
|
||
)
|
||
|
||
// 响应结构体
|
||
type (
|
||
// AuthenticateResponse 认证响应
|
||
AuthenticateResponse struct {
|
||
AccessToken string `json:"accessToken"`
|
||
ClientToken string `json:"clientToken"`
|
||
SelectedProfile map[string]interface{} `json:"selectedProfile,omitempty"`
|
||
AvailableProfiles []map[string]interface{} `json:"availableProfiles"`
|
||
User map[string]interface{} `json:"user,omitempty"`
|
||
}
|
||
|
||
// RefreshResponse 刷新令牌响应
|
||
RefreshResponse struct {
|
||
AccessToken string `json:"accessToken"`
|
||
ClientToken string `json:"clientToken"`
|
||
SelectedProfile map[string]interface{} `json:"selectedProfile,omitempty"`
|
||
User map[string]interface{} `json:"user,omitempty"`
|
||
}
|
||
)
|
||
|
||
type APIResponse struct {
|
||
Status int `json:"status"`
|
||
Data interface{} `json:"data"`
|
||
Error interface{} `json:"error"`
|
||
}
|
||
|
||
// standardResponse 生成标准响应
|
||
func standardResponse(c *gin.Context, status int, data interface{}, err interface{}) {
|
||
c.JSON(status, APIResponse{
|
||
Status: status,
|
||
Data: data,
|
||
Error: err,
|
||
})
|
||
}
|
||
|
||
// Authenticate 用户认证
|
||
func Authenticate(c *gin.Context) {
|
||
loggerInstance := logger.MustGetLogger()
|
||
db := database.MustGetDB()
|
||
|
||
// 读取并保存原始请求体,以便多次读取
|
||
rawData, err := io.ReadAll(c.Request.Body)
|
||
if err != nil {
|
||
loggerInstance.Error("[ERROR] 读取请求体失败: ", zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "读取请求体失败"})
|
||
return
|
||
}
|
||
c.Request.Body = io.NopCloser(bytes.NewBuffer(rawData))
|
||
|
||
// 绑定JSON数据到请求结构体
|
||
var request AuthenticateRequest
|
||
if err = c.ShouldBindJSON(&request); err != nil {
|
||
loggerInstance.Error("[ERROR] 解析认证请求失败: ", zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// 根据标识符类型(邮箱或用户名)获取用户
|
||
var userId int64
|
||
var profile *model.Profile
|
||
var UUID string
|
||
if emailRegex.MatchString(request.Identifier) {
|
||
userId, err = service.GetUserIDByEmail(db, request.Identifier)
|
||
} else {
|
||
profile, err = service.GetProfileByProfileName(db, request.Identifier)
|
||
if err != nil {
|
||
loggerInstance.Error("[ERROR] 用户名不存在: ", zap.String("标识符", request.Identifier), zap.Error(err))
|
||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
userId = profile.UserID
|
||
UUID = profile.UUID
|
||
}
|
||
|
||
if err != nil {
|
||
loggerInstance.Warn("[WARN] 认证失败: 用户不存在",
|
||
zap.String("标识符:", request.Identifier),
|
||
zap.Error(err))
|
||
|
||
return
|
||
}
|
||
|
||
// 验证密码
|
||
err = service.VerifyPassword(db, request.Password, userId)
|
||
if err != nil {
|
||
loggerInstance.Warn("[WARN] 认证失败:", zap.Error(err))
|
||
c.JSON(http.StatusForbidden, gin.H{"error": ErrWrongPassword})
|
||
return
|
||
}
|
||
// 生成新令牌
|
||
selectedProfile, availableProfiles, accessToken, clientToken, err := service.NewToken(db, loggerInstance, userId, UUID, request.ClientToken)
|
||
if err != nil {
|
||
loggerInstance.Error("[ERROR] 生成令牌失败:", zap.Error(err), zap.Any("用户ID:", userId))
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
user, err := service.GetUserByID(userId)
|
||
if err != nil {
|
||
loggerInstance.Error("[ERROR] id查找错误:", zap.Error(err), zap.Any("ID:", userId))
|
||
}
|
||
// 处理可用的配置文件
|
||
redisClient := redis.MustGetClient()
|
||
availableProfilesData := make([]map[string]interface{}, 0, len(availableProfiles))
|
||
for _, profile := range availableProfiles {
|
||
availableProfilesData = append(availableProfilesData, service.SerializeProfile(db, loggerInstance, redisClient, *profile))
|
||
}
|
||
response := AuthenticateResponse{
|
||
AccessToken: accessToken,
|
||
ClientToken: clientToken,
|
||
AvailableProfiles: availableProfilesData,
|
||
}
|
||
if selectedProfile != nil {
|
||
response.SelectedProfile = service.SerializeProfile(db, loggerInstance, redisClient, *selectedProfile)
|
||
}
|
||
if request.RequestUser {
|
||
// 使用 SerializeUser 来正确处理 Properties 字段
|
||
response.User = service.SerializeUser(loggerInstance, user, UUID)
|
||
}
|
||
|
||
// 返回认证响应
|
||
loggerInstance.Info("[INFO] 用户认证成功", zap.Any("用户ID:", userId))
|
||
c.JSON(http.StatusOK, response)
|
||
}
|
||
|
||
// ValidToken 验证令牌
|
||
func ValidToken(c *gin.Context) {
|
||
loggerInstance := logger.MustGetLogger()
|
||
db := database.MustGetDB()
|
||
|
||
var request ValidTokenRequest
|
||
if err := c.ShouldBindJSON(&request); err != nil {
|
||
loggerInstance.Error("[ERROR] 解析验证令牌请求失败: ", zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
// 验证令牌
|
||
if service.ValidToken(db, request.AccessToken, request.ClientToken) {
|
||
loggerInstance.Info("[INFO] 令牌验证成功", zap.Any("访问令牌:", request.AccessToken))
|
||
c.JSON(http.StatusNoContent, gin.H{"valid": true})
|
||
} else {
|
||
loggerInstance.Warn("[WARN] 令牌验证失败", zap.Any("访问令牌:", request.AccessToken))
|
||
c.JSON(http.StatusForbidden, gin.H{"valid": false})
|
||
}
|
||
}
|
||
|
||
// RefreshToken 刷新令牌
|
||
func RefreshToken(c *gin.Context) {
|
||
loggerInstance := logger.MustGetLogger()
|
||
db := database.MustGetDB()
|
||
|
||
var request RefreshRequest
|
||
if err := c.ShouldBindJSON(&request); err != nil {
|
||
loggerInstance.Error("[ERROR] 解析刷新令牌请求失败: ", zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// 获取用户ID和用户信息
|
||
UUID, err := service.GetUUIDByAccessToken(db, request.AccessToken)
|
||
if err != nil {
|
||
loggerInstance.Warn("[WARN] 刷新令牌失败: 无效的访问令牌", zap.Any("令牌:", request.AccessToken), zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
userID, _ := service.GetUserIDByAccessToken(db, request.AccessToken)
|
||
// 格式化UUID 这里是因为HMCL的传入参数是HEX格式,为了兼容HMCL,在此做处理
|
||
UUID = utils.FormatUUID(UUID)
|
||
|
||
profile, err := service.GetProfileByUUID(db, UUID)
|
||
if err != nil {
|
||
loggerInstance.Error("[ERROR] 刷新令牌失败: 无法获取用户信息 错误: ", zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// 准备响应数据
|
||
var profileData map[string]interface{}
|
||
var userData map[string]interface{}
|
||
var profileID string
|
||
|
||
// 处理选定的配置文件
|
||
if request.SelectedProfile != nil {
|
||
// 验证profileID是否存在
|
||
profileIDValue, ok := request.SelectedProfile["id"]
|
||
if !ok {
|
||
loggerInstance.Error("[ERROR] 刷新令牌失败: 缺少配置文件ID", zap.Any("ID:", userID))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "缺少配置文件ID"})
|
||
return
|
||
}
|
||
|
||
// 类型断言
|
||
profileID, ok = profileIDValue.(string)
|
||
if !ok {
|
||
loggerInstance.Error("[ERROR] 刷新令牌失败: 配置文件ID类型错误 ", zap.Any("用户ID:", userID))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "配置文件ID必须是字符串"})
|
||
return
|
||
}
|
||
|
||
// 格式化profileID
|
||
profileID = utils.FormatUUID(profileID)
|
||
|
||
// 验证配置文件所属用户
|
||
if profile.UserID != userID {
|
||
loggerInstance.Warn("[WARN] 刷新令牌失败: 用户不匹配 ", zap.Any("用户ID:", userID), zap.Any("配置文件用户ID:", profile.UserID))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": ErrUserNotMatch})
|
||
return
|
||
}
|
||
|
||
profileData = service.SerializeProfile(db, loggerInstance, redis.MustGetClient(), *profile)
|
||
}
|
||
user, _ := service.GetUserByID(userID)
|
||
// 添加用户信息(如果请求了)
|
||
if request.RequestUser {
|
||
userData = service.SerializeUser(loggerInstance, user, UUID)
|
||
}
|
||
|
||
// 刷新令牌
|
||
newAccessToken, newClientToken, err := service.RefreshToken(db, loggerInstance,
|
||
request.AccessToken,
|
||
request.ClientToken,
|
||
profileID,
|
||
)
|
||
if err != nil {
|
||
loggerInstance := logger.MustGetLogger()
|
||
loggerInstance.Error("[ERROR] 刷新令牌失败: ", zap.Error(err), zap.Any("用户ID: ", userID))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// 返回响应
|
||
loggerInstance.Info("[INFO] 刷新令牌成功", zap.Any("用户ID:", userID))
|
||
c.JSON(http.StatusOK, RefreshResponse{
|
||
AccessToken: newAccessToken,
|
||
ClientToken: newClientToken,
|
||
SelectedProfile: profileData,
|
||
User: userData,
|
||
})
|
||
}
|
||
|
||
// InvalidToken 使令牌失效
|
||
func InvalidToken(c *gin.Context) {
|
||
loggerInstance := logger.MustGetLogger()
|
||
db := database.MustGetDB()
|
||
|
||
var request ValidTokenRequest
|
||
if err := c.ShouldBindJSON(&request); err != nil {
|
||
loggerInstance.Error("[ERROR] 解析使令牌失效请求失败: ", zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
// 使令牌失效
|
||
service.InvalidToken(db, loggerInstance, request.AccessToken)
|
||
loggerInstance.Info("[INFO] 令牌已使失效", zap.Any("访问令牌:", request.AccessToken))
|
||
c.JSON(http.StatusNoContent, gin.H{})
|
||
}
|
||
|
||
// SignOut 用户登出
|
||
func SignOut(c *gin.Context) {
|
||
loggerInstance := logger.MustGetLogger()
|
||
db := database.MustGetDB()
|
||
|
||
var request SignOutRequest
|
||
if err := c.ShouldBindJSON(&request); err != nil {
|
||
loggerInstance.Error("[ERROR] 解析登出请求失败: %v", zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// 验证邮箱格式
|
||
if !emailRegex.MatchString(request.Email) {
|
||
loggerInstance.Warn("[WARN] 登出失败: 邮箱格式不正确 ", zap.Any(" ", request.Email))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": ErrInvalidEmailFormat})
|
||
return
|
||
}
|
||
|
||
// 通过邮箱获取用户
|
||
user, err := service.GetUserByEmail(request.Email)
|
||
if err != nil {
|
||
loggerInstance.Warn(
|
||
"登出失败: 用户不存在",
|
||
zap.String("邮箱", request.Email),
|
||
zap.Error(err),
|
||
)
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
// 验证密码
|
||
if err := service.VerifyPassword(db, request.Password, user.ID); err != nil {
|
||
loggerInstance.Warn("[WARN] 登出失败: 密码错误", zap.Any("用户ID:", user.ID))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": ErrWrongPassword})
|
||
return
|
||
}
|
||
|
||
// 使该用户的所有令牌失效
|
||
service.InvalidUserTokens(db, loggerInstance, user.ID)
|
||
loggerInstance.Info("[INFO] 用户登出成功", zap.Any("用户ID:", user.ID))
|
||
c.JSON(http.StatusNoContent, gin.H{"valid": true})
|
||
}
|
||
|
||
func GetProfileByUUID(c *gin.Context) {
|
||
loggerInstance := logger.MustGetLogger()
|
||
db := database.MustGetDB()
|
||
redisClient := redis.MustGetClient()
|
||
|
||
// 获取并格式化UUID
|
||
uuid := utils.FormatUUID(c.Param("uuid"))
|
||
loggerInstance.Info("[INFO] 接收到获取配置文件请求", zap.Any("UUID:", uuid))
|
||
|
||
// 获取配置文件
|
||
profile, err := service.GetProfileByUUID(db, uuid)
|
||
if err != nil {
|
||
loggerInstance.Error("[ERROR] 获取配置文件失败:", zap.Error(err), zap.String("UUID:", uuid))
|
||
standardResponse(c, http.StatusInternalServerError, nil, err.Error())
|
||
return
|
||
}
|
||
|
||
// 返回配置文件信息
|
||
loggerInstance.Info("[INFO] 成功获取配置文件", zap.String("UUID:", uuid), zap.String("名称:", profile.Name))
|
||
c.JSON(http.StatusOK, service.SerializeProfile(db, loggerInstance, redisClient, *profile))
|
||
}
|
||
|
||
func JoinServer(c *gin.Context) {
|
||
loggerInstance := logger.MustGetLogger()
|
||
db := database.MustGetDB()
|
||
redisClient := redis.MustGetClient()
|
||
|
||
var request JoinServerRequest
|
||
clientIP := c.ClientIP()
|
||
|
||
// 解析请求参数
|
||
if err := c.ShouldBindJSON(&request); err != nil {
|
||
loggerInstance.Error(
|
||
"解析加入服务器请求失败",
|
||
zap.Error(err),
|
||
zap.String("IP", clientIP),
|
||
)
|
||
standardResponse(c, http.StatusBadRequest, nil, ErrInvalidRequest)
|
||
return
|
||
}
|
||
|
||
loggerInstance.Info(
|
||
"收到加入服务器请求",
|
||
zap.String("服务器ID", request.ServerID),
|
||
zap.String("用户UUID", request.SelectedProfile),
|
||
zap.String("IP", clientIP),
|
||
)
|
||
|
||
// 处理加入服务器请求
|
||
if err := service.JoinServer(db, loggerInstance, redisClient, request.ServerID, request.AccessToken, request.SelectedProfile, clientIP); err != nil {
|
||
loggerInstance.Error(
|
||
"加入服务器失败",
|
||
zap.Error(err),
|
||
zap.String("服务器ID", request.ServerID),
|
||
zap.String("用户UUID", request.SelectedProfile),
|
||
zap.String("IP", clientIP),
|
||
)
|
||
standardResponse(c, http.StatusInternalServerError, nil, ErrJoinServerFailed)
|
||
return
|
||
}
|
||
|
||
// 加入成功,返回204状态码
|
||
loggerInstance.Info(
|
||
"加入服务器成功",
|
||
zap.String("服务器ID", request.ServerID),
|
||
zap.String("用户UUID", request.SelectedProfile),
|
||
zap.String("IP", clientIP),
|
||
)
|
||
c.Status(http.StatusNoContent)
|
||
}
|
||
|
||
func HasJoinedServer(c *gin.Context) {
|
||
loggerInstance := logger.MustGetLogger()
|
||
db := database.MustGetDB()
|
||
redisClient := redis.MustGetClient()
|
||
|
||
clientIP, _ := c.GetQuery("ip")
|
||
|
||
// 获取并验证服务器ID参数
|
||
serverID, exists := c.GetQuery("serverId")
|
||
if !exists || serverID == "" {
|
||
loggerInstance.Warn("[WARN] 缺少服务器ID参数", zap.Any("IP:", clientIP))
|
||
standardResponse(c, http.StatusNoContent, nil, ErrServerIDRequired)
|
||
return
|
||
}
|
||
|
||
// 获取并验证用户名参数
|
||
username, exists := c.GetQuery("username")
|
||
if !exists || username == "" {
|
||
loggerInstance.Warn("[WARN] 缺少用户名参数", zap.Any("服务器ID:", serverID), zap.Any("IP:", clientIP))
|
||
standardResponse(c, http.StatusNoContent, nil, ErrUsernameRequired)
|
||
return
|
||
}
|
||
|
||
loggerInstance.Info("[INFO] 收到会话验证请求", zap.Any("服务器ID:", serverID), zap.Any("用户名: ", username), zap.Any("IP: ", clientIP))
|
||
|
||
// 验证玩家是否已加入服务器
|
||
if err := service.HasJoinedServer(loggerInstance, redisClient, serverID, username, clientIP); err != nil {
|
||
loggerInstance.Warn("[WARN] 会话验证失败",
|
||
zap.Error(err),
|
||
zap.String("serverID", serverID),
|
||
zap.String("username", username),
|
||
zap.String("clientIP", clientIP),
|
||
)
|
||
standardResponse(c, http.StatusNoContent, nil, ErrSessionVerifyFailed)
|
||
return
|
||
}
|
||
|
||
profile, err := service.GetProfileByUUID(db, username)
|
||
if err != nil {
|
||
loggerInstance.Error("[ERROR] 获取用户配置文件失败: %v - 用户名: %s",
|
||
zap.Error(err), // 错误详情(zap 原生支持,保留错误链)
|
||
zap.String("username", username), // 结构化存储用户名(便于检索)
|
||
)
|
||
standardResponse(c, http.StatusNoContent, nil, ErrProfileNotFound)
|
||
return
|
||
}
|
||
|
||
// 返回玩家配置文件
|
||
loggerInstance.Info("[INFO] 会话验证成功 - 服务器ID: %s, 用户名: %s, UUID: %s",
|
||
zap.String("serverID", serverID), // 结构化存储服务器ID
|
||
zap.String("username", username), // 结构化存储用户名
|
||
zap.String("UUID", profile.UUID), // 结构化存储UUID
|
||
)
|
||
c.JSON(200, service.SerializeProfile(db, loggerInstance, redisClient, *profile))
|
||
}
|
||
|
||
func GetProfilesByName(c *gin.Context) {
|
||
loggerInstance := logger.MustGetLogger()
|
||
db := database.MustGetDB()
|
||
|
||
var names []string
|
||
|
||
// 解析请求参数
|
||
if err := c.ShouldBindJSON(&names); err != nil {
|
||
loggerInstance.Error("[ERROR] 解析名称数组请求失败: ",
|
||
zap.Error(err),
|
||
)
|
||
standardResponse(c, http.StatusBadRequest, nil, ErrInvalidParams)
|
||
return
|
||
}
|
||
loggerInstance.Info("[INFO] 接收到批量获取配置文件请求",
|
||
zap.Int("名称数量:", len(names)), // 结构化存储名称数量
|
||
)
|
||
|
||
// 批量获取配置文件
|
||
profiles, err := service.GetProfilesDataByNames(db, names)
|
||
if err != nil {
|
||
loggerInstance.Error("[ERROR] 获取配置文件失败: ",
|
||
zap.Error(err),
|
||
)
|
||
}
|
||
|
||
// 改造:zap 兼容原有 INFO 日志格式
|
||
loggerInstance.Info("[INFO] 成功获取配置文件",
|
||
zap.Int("请求名称数:", len(names)),
|
||
zap.Int("返回结果数: ", len(profiles)),
|
||
)
|
||
|
||
c.JSON(http.StatusOK, profiles)
|
||
}
|
||
|
||
func GetMetaData(c *gin.Context) {
|
||
loggerInstance := logger.MustGetLogger()
|
||
redisClient := redis.MustGetClient()
|
||
|
||
meta := gin.H{
|
||
"implementationName": "CellAuth",
|
||
"implementationVersion": "0.0.1",
|
||
"serverName": "LittleLan's Yggdrasil Server Implementation.",
|
||
"links": gin.H{
|
||
"homepage": "https://skin.littlelan.cn",
|
||
"register": "https://skin.littlelan.cn/auth",
|
||
},
|
||
"feature.non_email_login": true,
|
||
"feature.enable_profile_key": true,
|
||
}
|
||
skinDomains := []string{".hitwh.games", ".littlelan.cn"}
|
||
signature, err := service.GetPublicKeyFromRedisFunc(loggerInstance, redisClient)
|
||
if err != nil {
|
||
loggerInstance.Error("[ERROR] 获取公钥失败: ", zap.Error(err))
|
||
standardResponse(c, http.StatusInternalServerError, nil, ErrInternalServer)
|
||
return
|
||
}
|
||
|
||
loggerInstance.Info("[INFO] 提供元数据")
|
||
c.JSON(http.StatusOK, gin.H{"meta": meta,
|
||
"skinDomains": skinDomains,
|
||
"signaturePublickey": signature})
|
||
}
|
||
|
||
func GetPlayerCertificates(c *gin.Context) {
|
||
loggerInstance := logger.MustGetLogger()
|
||
db := database.MustGetDB()
|
||
redisClient := redis.MustGetClient()
|
||
|
||
var uuid string
|
||
authHeader := c.GetHeader("Authorization")
|
||
if authHeader == "" {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header not provided"})
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
// 检查是否以 Bearer 开头并提取 sessionID
|
||
bearerPrefix := "Bearer "
|
||
if len(authHeader) < len(bearerPrefix) || authHeader[:len(bearerPrefix)] != bearerPrefix {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid Authorization format"})
|
||
c.Abort()
|
||
return
|
||
}
|
||
tokenID := authHeader[len(bearerPrefix):]
|
||
if tokenID == "" {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid Authorization format"})
|
||
c.Abort()
|
||
return
|
||
}
|
||
var err error
|
||
uuid, err = service.GetUUIDByAccessToken(db, tokenID)
|
||
|
||
if uuid == "" {
|
||
loggerInstance.Error("[ERROR] 获取玩家UUID失败: ", zap.Error(err))
|
||
standardResponse(c, http.StatusInternalServerError, nil, ErrInternalServer)
|
||
return
|
||
}
|
||
|
||
// 格式化UUID
|
||
uuid = utils.FormatUUID(uuid)
|
||
|
||
// 生成玩家证书
|
||
certificate, err := service.GeneratePlayerCertificate(db, loggerInstance, redisClient, uuid)
|
||
if err != nil {
|
||
loggerInstance.Error("[ERROR] 生成玩家证书失败: ", zap.Error(err))
|
||
standardResponse(c, http.StatusInternalServerError, nil, ErrInternalServer)
|
||
return
|
||
}
|
||
|
||
loggerInstance.Info("[INFO] 成功生成玩家证书")
|
||
c.JSON(http.StatusOK, certificate)
|
||
}
|