199 lines
8.5 KiB
Go
199 lines
8.5 KiB
Go
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
|
||
}
|