package errors import ( "errors" "fmt" "strings" "time" pb "github.com/topfans/backend/pkg/proto/common" ) // 业务错误类型 var ( ErrUserNotFound = errors.New("user not found") ErrUserAlreadyExists = errors.New("user already exists") ErrInvalidPassword = errors.New("invalid password") ErrInvalidOldPassword = errors.New("old password is incorrect") ErrSameAsOldPassword = errors.New("new password is same as old") ErrInvalidVerifyToken = errors.New("invalid verify token") ErrInvalidToken = errors.New("invalid token") ErrTokenExpired = errors.New("token expired") ErrTokenMismatch = errors.New("token mismatch") ErrUserInactive = errors.New("user is inactive") ErrFanProfileNotFound = errors.New("fan profile not found") ErrFanProfileAlreadyExists = errors.New("fan profile already exists") ErrNicknameAlreadyExists = errors.New("该昵称已被注册") ErrInvalidNickname = errors.New("invalid nickname format") ErrStarNotFound = errors.New("star not found") ErrInvalidMobile = errors.New("invalid mobile format") ErrPasswordTooShort = errors.New("password too short") ErrInvalidStarID = errors.New("invalid star_id") ErrInvalidUserID = errors.New("invalid user_id") ErrMaxIdentitiesReached = errors.New("maximum number of identities reached") ErrInternalServer = errors.New("internal server error") // 社交服务相关错误 ErrCannotAddSelf = errors.New("不能添加自己为好友") ErrCannotSearchSelf = errors.New("不能查找自己") ErrNotFanOfStar = errors.New("对方不是该明星的粉丝") ErrAlreadyFriends = errors.New("你们已经是好友了") ErrRequestAlreadyPending = errors.New("已有待处理的好友请求") ErrRequestInCooldown = errors.New("请求已被拒绝,请稍后再试") ErrInvalidFriendUserID = errors.New("friend_user_id不能为空") ErrFriendRequestNotFound = errors.New("好友请求不存在") ErrCannotProcessOwnRequest = errors.New("不能处理自己发送的请求") ErrRequestAlreadyProcessed = errors.New("该请求已被处理") ErrRequestExpired = errors.New("好友请求已过期") ErrInvalidAction = errors.New("无效的操作") ErrNotFriends = errors.New("你们不是好友") ErrMaxFriendsReached = errors.New("已达到最大好友数量限制") // 资产服务相关错误 ErrAssetNotFound = errors.New("资产不存在") ErrMintOrderNotFound = errors.New("铸造订单不存在") ErrInsufficientCrystal = errors.New("水晶余额不足") ErrInsufficientMintTimes = errors.New("铸造次数不足") ErrAssetAccessDenied = errors.New("无权访问该资产") ErrMintOrderAccessDenied = errors.New("无权访问该订单") ErrInvalidAssetStatus = errors.New("资产状态无效") ErrInvalidMintOrderStatus = errors.New("订单状态无效") // 账号状态相关错误 ErrAccountFrozen = errors.New("账号已被冻结") ErrAccountBanned = errors.New("账号已被封禁") // 活动服务相关错误 ErrActivityNotFound = errors.New("活动不存在") ErrActivityItemNotFound = errors.New("活动道具不存在") // 星册服务相关错误 ErrCollectionAssetNotFound = errors.New("典藏藏品不存在") ErrActivityAssetNotFound = errors.New("活动藏品不存在") ErrAssetRegistryNotFound = errors.New("资产索引不存在") ErrInvalidAssetType = errors.New("无效的资产类型") ) // ToStatusCode 将错误转换为Proto状态码 // 用 errors.Is 而非 == 比较,以便识别 fmt.Errorf("%w: ...", ErrXxx, ...) 包装后的错误 func ToStatusCode(err error) pb.StatusCode { if err == nil { return pb.StatusCode_STATUS_OK } switch { case errors.Is(err, ErrUserNotFound), errors.Is(err, ErrFanProfileNotFound), errors.Is(err, ErrStarNotFound): return pb.StatusCode_STATUS_NOT_FOUND case errors.Is(err, ErrUserAlreadyExists), errors.Is(err, ErrFanProfileAlreadyExists), errors.Is(err, ErrNicknameAlreadyExists): return pb.StatusCode_STATUS_BAD_REQUEST case errors.Is(err, ErrInvalidPassword), errors.Is(err, ErrInvalidToken), errors.Is(err, ErrTokenExpired), errors.Is(err, ErrTokenMismatch): return pb.StatusCode_STATUS_UNAUTHORIZED case errors.Is(err, ErrAccountFrozen), errors.Is(err, ErrAccountBanned), errors.Is(err, ErrUserInactive): return pb.StatusCode_STATUS_FORBIDDEN case errors.Is(err, ErrInvalidMobile), errors.Is(err, ErrPasswordTooShort), errors.Is(err, ErrInvalidOldPassword), errors.Is(err, ErrSameAsOldPassword), errors.Is(err, ErrInvalidVerifyToken), errors.Is(err, ErrInvalidStarID), errors.Is(err, ErrInvalidUserID), errors.Is(err, ErrMaxIdentitiesReached), errors.Is(err, ErrInvalidNickname): return pb.StatusCode_STATUS_BAD_REQUEST case errors.Is(err, ErrCannotAddSelf), errors.Is(err, ErrCannotSearchSelf), errors.Is(err, ErrNotFanOfStar), errors.Is(err, ErrAlreadyFriends), errors.Is(err, ErrRequestAlreadyPending), errors.Is(err, ErrInvalidFriendUserID), errors.Is(err, ErrCannotProcessOwnRequest), errors.Is(err, ErrRequestAlreadyProcessed), errors.Is(err, ErrRequestExpired), errors.Is(err, ErrInvalidAction), errors.Is(err, ErrNotFriends): return pb.StatusCode_STATUS_BAD_REQUEST case errors.Is(err, ErrRequestInCooldown): return pb.StatusCode_STATUS_TOO_MANY_REQUESTS case errors.Is(err, ErrFriendRequestNotFound), errors.Is(err, ErrAssetNotFound), errors.Is(err, ErrMintOrderNotFound): return pb.StatusCode_STATUS_NOT_FOUND case errors.Is(err, ErrInsufficientCrystal), errors.Is(err, ErrInsufficientMintTimes), errors.Is(err, ErrInvalidAssetStatus), errors.Is(err, ErrInvalidMintOrderStatus): return pb.StatusCode_STATUS_BAD_REQUEST case errors.Is(err, ErrAssetAccessDenied), errors.Is(err, ErrMintOrderAccessDenied): return pb.StatusCode_STATUS_FORBIDDEN case errors.Is(err, ErrActivityNotFound), errors.Is(err, ErrActivityItemNotFound), errors.Is(err, ErrCollectionAssetNotFound), errors.Is(err, ErrActivityAssetNotFound), errors.Is(err, ErrAssetRegistryNotFound): return pb.StatusCode_STATUS_NOT_FOUND case errors.Is(err, ErrInvalidAssetType): return pb.StatusCode_STATUS_BAD_REQUEST default: return pb.StatusCode_STATUS_INTERNAL_ERROR } } // FormatErrorResponse 格式化错误响应 func FormatErrorResponse(err error) *pb.BaseResponse { if err == nil { return &pb.BaseResponse{ Code: pb.StatusCode_STATUS_OK, Message: "", Timestamp: getCurrentTimestamp(), } } return &pb.BaseResponse{ Code: ToStatusCode(err), Message: err.Error(), Timestamp: getCurrentTimestamp(), } } // FormatSuccessResponse 格式化成功响应 func FormatSuccessResponse() *pb.BaseResponse { return &pb.BaseResponse{ Code: pb.StatusCode_STATUS_OK, Message: "", Timestamp: getCurrentTimestamp(), } } // BuildBaseResponse 构建基础响应(FormatErrorResponse的别名) func BuildBaseResponse(err error) *pb.BaseResponse { return FormatErrorResponse(err) } // NewError 创建新的业务错误 func NewError(code pb.StatusCode, message string) error { return fmt.Errorf("%s", message) } // NewRequestInCooldownError 创建冷静期错误,包含剩余天数 func NewRequestInCooldownError(days int) error { return fmt.Errorf("请求已被拒绝,请 %d 天后再试", days) } // BuildBaseResponseWithMessage 构建带有自定义消息的响应 func BuildBaseResponseWithMessage(code pb.StatusCode, message string) *pb.BaseResponse { return &pb.BaseResponse{ Code: code, Message: message, Timestamp: getCurrentTimestamp(), } } // getCurrentTimestamp 获取当前时间戳(毫秒) func getCurrentTimestamp() int64 { return time.Now().UnixMilli() } // NewAccountBannedError 返回 ErrAccountBanned,可选地把 reason 拼到 message 里 // 用 fmt.Errorf("%w: ...") 保留 wrap 关系,让 errors.Is(err, ErrAccountBanned) 仍能识别 func NewAccountBannedError(reason string) error { if reason != "" { return fmt.Errorf("%w: %s", ErrAccountBanned, reason) } return ErrAccountBanned } // NewAccountFrozenError 返回 ErrAccountFrozen,把 reason / frozenUntil 拼到 message 里 // frozenUntil 为 nil 表示永久冻结 func NewAccountFrozenError(reason string, frozenUntil *int64) error { parts := []string{} if reason != "" { parts = append(parts, "原因:"+reason) } if frozenUntil != nil { parts = append(parts, "解封时间:"+time.UnixMilli(*frozenUntil).Format("2006-01-02 15:04:05")) } if len(parts) > 0 { return fmt.Errorf("%w: %s", ErrAccountFrozen, strings.Join(parts, ", ")) } return ErrAccountFrozen }