Files
backend/internal/errors/errors.go

187 lines
6.4 KiB
Go
Raw Normal View History

// Package errors 定义应用程序的错误类型
package errors
import (
"errors"
"fmt"
)
// 预定义错误
var (
// 用户相关错误
ErrUserNotFound = errors.New("用户不存在")
ErrUserAlreadyExists = errors.New("用户已存在")
ErrEmailAlreadyExists = errors.New("邮箱已被注册")
ErrAccountDisabled = errors.New("账号已被禁用")
// 认证相关错误
refactor(yggdrasil): 整理与性能优化,修复若干 Bug 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,与前端约定对齐。
2026-07-09 17:18:26 +08:00
ErrUnauthorized = errors.New("未授权")
ErrInvalidToken = errors.New("无效的令牌")
ErrTokenExpired = errors.New("令牌已过期")
// 档案相关错误
ErrProfileNotFound = errors.New("档案不存在")
ErrProfileNameExists = errors.New("角色名已被使用")
ErrProfileLimitReached = errors.New("已达档案数量上限")
ErrProfileNoPermission = errors.New("无权操作此档案")
// 材质相关错误
ErrTextureNotFound = errors.New("材质不存在")
ErrTextureExists = errors.New("该材质已存在")
ErrTextureLimitReached = errors.New("已达材质数量上限")
ErrTextureNoPermission = errors.New("无权操作此材质")
// 验证码相关错误
ErrInvalidVerificationCode = errors.New("验证码错误或已过期")
ErrTooManyAttempts = errors.New("尝试次数过多")
ErrSendTooFrequent = errors.New("发送过于频繁")
// URL验证相关错误
ErrInvalidURL = errors.New("无效的URL格式")
ErrDomainNotAllowed = errors.New("URL域名不在允许的列表中")
// 存储相关错误
ErrStorageUnavailable = errors.New("存储服务不可用")
ErrUploadFailed = errors.New("上传失败")
2025-12-03 10:58:39 +08:00
// Yggdrasil相关错误
ErrPasswordMismatch = errors.New("密码错误")
ErrPasswordNotSet = errors.New("未生成密码")
ErrInvalidServerID = errors.New("服务器ID格式无效")
ErrSessionNotFound = errors.New("会话不存在或已过期")
ErrSessionMismatch = errors.New("会话验证失败")
ErrUsernameMismatch = errors.New("用户名不匹配")
refactor(yggdrasil): 整理与性能优化,修复若干 Bug 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,与前端约定对齐。
2026-07-09 17:18:26 +08:00
ErrUsernameRequired = errors.New("用户名不能为空")
2025-12-03 10:58:39 +08:00
ErrIPMismatch = errors.New("IP地址不匹配")
ErrInvalidAccessToken = errors.New("访问令牌无效")
ErrProfileMismatch = errors.New("selectedProfile与Token不匹配")
ErrUUIDRequired = errors.New("UUID不能为空")
// 通用错误
ErrBadRequest = errors.New("请求参数错误")
ErrInternalServer = errors.New("服务器内部错误")
ErrNotFound = errors.New("资源不存在")
ErrForbidden = errors.New("权限不足")
)
// AppError 应用错误类型,包含错误码和消息
type AppError struct {
Code int // HTTP状态码
Message string // 用户可见的错误消息
Err error // 原始错误(用于日志)
}
// Error 实现error接口
func (e *AppError) Error() string {
if e.Err != nil {
return fmt.Sprintf("%s: %v", e.Message, e.Err)
}
return e.Message
}
// Unwrap 支持errors.Is和errors.As
func (e *AppError) Unwrap() error {
return e.Err
}
// NewAppError 创建新的应用错误
func NewAppError(code int, message string, err error) *AppError {
return &AppError{
Code: code,
Message: message,
Err: err,
}
}
// NewBadRequest 创建400错误
func NewBadRequest(message string, err error) *AppError {
return NewAppError(400, message, err)
}
// NewUnauthorized 创建401错误
func NewUnauthorized(message string) *AppError {
return NewAppError(401, message, nil)
}
// NewForbidden 创建403错误
func NewForbidden(message string) *AppError {
return NewAppError(403, message, nil)
}
// NewNotFound 创建404错误
func NewNotFound(message string) *AppError {
return NewAppError(404, message, nil)
}
// NewInternalError 创建500错误
func NewInternalError(message string, err error) *AppError {
return NewAppError(500, message, err)
}
// 注意:原此处的 Is/As/Wrap 函数(透传标准库)已删除。
// 请直接使用标准库 errors.Is / errors.As / fmt.Errorf。
// YggdrasilErrorResponse Yggdrasil协议标准错误响应格式
type YggdrasilErrorResponse struct {
Error string `json:"error"` // 错误的简要描述(机器可读)
ErrorMessage string `json:"errorMessage"` // 错误的详细信息(人类可读)
Cause string `json:"cause,omitempty"` // 该错误的原因(可选)
}
// NewYggdrasilErrorResponse 创建Yggdrasil标准错误响应
func NewYggdrasilErrorResponse(error, errorMessage, cause string) *YggdrasilErrorResponse {
return &YggdrasilErrorResponse{
Error: error,
ErrorMessage: errorMessage,
Cause: cause,
}
}
// YggdrasilErrorCodes Yggdrasil协议错误码常量
const (
// ForbiddenOperationException 错误消息
YggErrInvalidToken = "Invalid token."
YggErrInvalidCredentials = "Invalid credentials. Invalid username or password."
// IllegalArgumentException 错误消息
YggErrProfileAlreadyAssigned = "Access token already has a profile assigned."
)
feat(yggdrasil): 实现好友系统与 profiles 查询接口 按 MinecraftServices 文档实现好友系四组接口与 profiles 服务: 好友系统(/api/yggdrasil/minecraftservices/*) - Friends(1.3):列表查询、ADD/REMOVE 操作(发请求/接受/拒绝/撤回/删除) - 状态枚举+单向记录模型,接受请求时合并两方向避免好友列表重复 - Player Attributes(1.2):friendsPreferences / 脏词过滤 / 聊天偏好读写 - Upsert 用 map 显式写字段规避 gorm 对带 default 零值布尔字段的忽略 - Presence(1.1):Redis key+TTL 上报与好友在线状态批量查询 - Blocklist(1.6):屏蔽列表查询(含 120s Redis 缓存),Block/Unblock 内部方法 Profiles 服务 - getManyByName(2.1):POST /api/profiles/minecraft,返回 [{id,name}] - toLowerCase 规范化、去重、空名过滤、maxBatch=10 超限拒绝 - getByName(2.2):GET /api/users/profiles/minecraft/:name,返回 {id,name},未找到返回 404 路由与基础设施 - 路由按官方 host 前缀一一转发 - api.mojang.com/* -> /api/yggdrasil/api/* - api.minecraftservices.com/* -> /api/yggdrasil/minecraftservices/* - 新增 Friend/PlayerAttributes 模型与 AutoMigrate 注册 - 新增 FriendsService 与 Container 装配 - 抽 extractBearerToken helper,统一 MinecraftServices 错误响应 修复 - texture_service: ToggleFavorite 在 db 为 nil 时降级非事务执行 修复 TestTextureServiceImpl_ToggleFavorite 空指针 panic - texture_service_test: UploadTexture 用例改用真实 SHA-256 命中 mock 修复 4 个子用例因文件大小/Hash 不匹配的失败 - profile_repository: GetByNames/FindByName 改 LOWER() 大小写不敏感 与客户端 toLowerCase 规范化对齐 测试 - 好友模型、ADD/REMOVE/接受、属性、屏蔽、Bearer 解析单测全绿 - profiles 查询规范化、去重、超限、大小写不敏感单测全绿 - go build ./... && go test ./... 全部通过
2026-07-09 20:44:41 +08:00
// FriendsErrorStatus MinecraftServices 好友系接口业务错误码(文档 1.3.4
const (
FriendsErrUnknownProfile = "UNKNOWN_PROFILE"
FriendsErrCannotAddSelf = "CANNOT_ADD_SELF"
FriendsErrDuplicatedProfiles = "DUPLICATED_PROFILES"
)
// FriendsErrorResultCode MinecraftServices 好友系接口结果码(文档 4
const (
FriendsResultSuccess = "SUCCESS"
FriendsResultError = "ERROR"
FriendsResultServiceUnavailable = "SERVICE_NOT_AVAILABLE"
FriendsResultTooManyRequests = "TOO_MANY_REQUESTS"
FriendsResultForbidden = "FORBIDDEN"
FriendsResultUnknownProfile = "UNKNOWN_PROFILE"
FriendsResultUnauthorized = "UNAUTHORIZED"
)
// MinecraftServicesErrorResponse MinecraftServices 系接口标准错误响应(文档 0.3
// 与 YggdrasilErrorResponse 不同,本体系按 MinecraftServices 的结构返回:
// { "path": "...", "error": "...", "errorMessage": "...", "details": {} }
type MinecraftServicesErrorResponse struct {
Path string `json:"path"`
Error string `json:"error"` // 机器可读错误码
ErrorMessage string `json:"errorMessage"` // 人类可读描述
Details interface{} `json:"details,omitempty"` // 错误特定细节
}
// NewMinecraftServicesErrorResponse 创建 MinecraftServices 系标准错误响应
func NewMinecraftServicesErrorResponse(path, errorCode, errorMessage string, details interface{}) *MinecraftServicesErrorResponse {
return &MinecraftServicesErrorResponse{
Path: path,
Error: errorCode,
ErrorMessage: errorMessage,
Details: details,
}
}