Bug 修复
- textures metadata:SKIN 仅在 IsSlim 时输出 {"model":"slim"};CAPE 不带 metadata。
原实现误用文件字节数 skin.Size 作为 metadata,违反 Yggdrasil 协议。
- KeyPair 持久化不全:Profile 新增 rsa_public_key/public_key_signature/
public_key_signature_v2/key_expires_at/key_refresh_at 列;GetKeyPair/UpdateKeyPair
读写全部字段。AutoMigrate 自动加列。原实现每次请求都重新生成 RSA-4096。
- GetPlayerCertificates:无效 token 返回 401(先前误用 403 且未判 err)。
- HasJoinedServer:失败返回 204 无 body,符合 Yggdrasil 协议(原用 APIResponse
+ body 违反 204 规范)。
代码质量
- Authenticate 删除冗余 body 读取与回放。
- 证书服务引入具名结构 PlayerCertificate/PlayerKeyPair,替代 map[string]interface{}。
- CreateSession 用户名缺失改用 ErrUsernameRequired(新增)。
性能优化(签名一致性的回归测试已覆盖)
- SignatureService 缓存已解析的根私钥(sync.RWMutex 双重检查),
快路径仅一次 RLock + RSA 签名,免去每次签名 PEM 解析与 Redis 往返。
- GetOrCreateYggdrasilKeyPair 用 MGet 单次往返取三字段,缓存命中后零 Redis。
- NewKeyPair 消息构造用 strconv.AppendInt 避免 string+拼接分配;V2 复用 DER 字节。
- 序列化服务:texturesMap 预分配 cap(2);slim 分支直接构造完整 map。
- 会话服务 GetSession 移除重复的 ValidateServerID 校验。
死代码清理
- 删除 yggdrasil_validator.go(未被调用的 Validator)。
- 删除 signature_service.FormatPublicKey/SignStringWithProfileRSA。
- 删除 pkg/auth 中未被使用的 YggdrasilJWTManager 与 GenerateKeyPair/
EncodePrivateKeyToPEM/RedisClient/YggdrasilPrivateKeyRedisKey。
- 删除 yggdrasil_handler.go 中 25 个未使用常量、passwordRegex、
APIResponse/standardResponse。
- 删除 errors.go 中未被使用的 ErrYggForbiddenOperation/ErrYggIllegalArgument/
ErrInvalidSignature/ErrInvalidTextureType/ErrCertificateGenerate/ErrInvalidPassword。
测试
- 新增 signature_service_test.go(基于 miniredis):8 个测试 + 基准。
核心:SignString_MatchesReference 与重构前参考实现逐字节比对签名输出一致。
-race 检测通过;基准约 7.1ms/op(缓存命中路径)。
附带(与本次任务前已存在的未提交改动)
- handler/captcha_handler:将响应字段 msg 改为 message,与前端约定对齐。
109 lines
2.7 KiB
Go
109 lines
2.7 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"carrotskin/internal/container"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// CaptchaHandler 验证码处理器
|
|
type CaptchaHandler struct {
|
|
container *container.Container
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewCaptchaHandler 创建CaptchaHandler实例
|
|
func NewCaptchaHandler(c *container.Container) *CaptchaHandler {
|
|
return &CaptchaHandler{
|
|
container: c,
|
|
logger: c.Logger,
|
|
}
|
|
}
|
|
|
|
// CaptchaVerifyRequest 验证码验证请求
|
|
type CaptchaVerifyRequest struct {
|
|
CaptchaID string `json:"captchaId" binding:"required"`
|
|
Dx int `json:"dx" binding:"required"`
|
|
}
|
|
|
|
// Generate 生成验证码
|
|
// @Summary 生成滑动验证码
|
|
// @Description 生成滑动验证码图片
|
|
// @Tags captcha
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} map[string]interface{} "生成成功 {code: 200, message: string, data: {masterImage, tileImage, captchaId, y}}"
|
|
// @Failure 500 {object} map[string]interface{} "生成失败"
|
|
// @Router /api/v1/captcha/generate [get]
|
|
func (h *CaptchaHandler) Generate(c *gin.Context) {
|
|
masterImg, tileImg, captchaID, y, err := h.container.CaptchaService.Generate(c.Request.Context())
|
|
if err != nil {
|
|
h.logger.Error("生成验证码失败", zap.Error(err))
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"code": 500,
|
|
"message": "生成验证码失败",
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"code": 200,
|
|
"message": "操作成功",
|
|
"data": gin.H{
|
|
"masterImage": masterImg,
|
|
"tileImage": tileImg,
|
|
"captchaId": captchaID,
|
|
"y": y,
|
|
},
|
|
})
|
|
}
|
|
|
|
// Verify 验证验证码
|
|
// @Summary 验证滑动验证码
|
|
// @Description 验证用户滑动的偏移量是否正确
|
|
// @Tags captcha
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body CaptchaVerifyRequest true "验证请求"
|
|
// @Success 200 {object} map[string]interface{} "验证结果 {code: 200/400, message: string}"
|
|
// @Failure 400 {object} map[string]interface{} "参数错误"
|
|
// @Router /api/v1/captcha/verify [post]
|
|
func (h *CaptchaHandler) Verify(c *gin.Context) {
|
|
var req CaptchaVerifyRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"code": 400,
|
|
"message": "参数错误: " + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
valid, err := h.container.CaptchaService.Verify(c.Request.Context(), req.Dx, req.CaptchaID)
|
|
if err != nil {
|
|
h.logger.Error("验证码验证失败",
|
|
zap.String("captcha_id", req.CaptchaID),
|
|
zap.Error(err),
|
|
)
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"code": 500,
|
|
"message": "验证失败",
|
|
})
|
|
return
|
|
}
|
|
|
|
if valid {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"code": 200,
|
|
"message": "验证成功",
|
|
})
|
|
} else {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"code": 400,
|
|
"message": "验证失败,请重试",
|
|
})
|
|
}
|
|
}
|