207 lines
8.9 KiB
Go
207 lines
8.9 KiB
Go
package xerr
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
)
|
||
|
||
// Code 是服务内部稳定错误原因。
|
||
// 它对应 google.rpc.ErrorInfo.reason,而不是 HTTP code 或 gRPC code。
|
||
type Code string
|
||
|
||
const (
|
||
// InvalidArgument 表示请求字段、格式或领域约束不合法。
|
||
InvalidArgument Code = "INVALID_ARGUMENT"
|
||
// NotFound 表示目标实体不存在。
|
||
NotFound Code = "NOT_FOUND"
|
||
// Conflict 表示唯一键、版本、幂等或状态冲突。
|
||
Conflict Code = "CONFLICT"
|
||
// RoomClosed 表示房间已被后台关闭,客户端不能再进入。
|
||
RoomClosed Code = "ROOM_CLOSED"
|
||
// Unauthorized 表示身份不存在或认证失败。
|
||
Unauthorized Code = "UNAUTHORIZED"
|
||
// PermissionDenied 表示已认证但没有执行权限。
|
||
PermissionDenied Code = "PERMISSION_DENIED"
|
||
// ProfileRequired 表示已登录用户尚未完成 App 最小注册资料,不能进入门禁保护业务。
|
||
ProfileRequired Code = "PROFILE_REQUIRED"
|
||
// Unavailable 表示依赖或当前服务临时不可用。
|
||
Unavailable Code = "UNAVAILABLE"
|
||
// Internal 表示不能安全暴露更细原因的内部失败。
|
||
Internal Code = "INTERNAL_ERROR"
|
||
|
||
// AuthFailed 统一短号不存在、未设置密码、密码错误和三方凭证拒绝,避免身份枚举。
|
||
AuthFailed Code = "AUTH_FAILED"
|
||
// AuthLoginBlocked 表示登录入口命中地区、语言、时区或 IP 风控策略。
|
||
AuthLoginBlocked Code = "AUTH_LOGIN_BLOCKED"
|
||
// PasswordAlreadySet 表示用户已经设置过密码,不能重复初始化密码身份。
|
||
PasswordAlreadySet Code = "PASSWORD_ALREADY_SET"
|
||
// UserDisabled 表示用户被禁用或封禁。
|
||
UserDisabled Code = "USER_DISABLED"
|
||
// SessionExpired 表示 refresh token 或会话已过期。
|
||
SessionExpired Code = "SESSION_EXPIRED"
|
||
// SessionRevoked 表示 refresh token 或会话已被主动失效。
|
||
SessionRevoked Code = "SESSION_REVOKED"
|
||
// DisplayUserIDInvalid 表示短号格式不符合 user-service 规则。
|
||
DisplayUserIDInvalid Code = "DISPLAY_USER_ID_INVALID"
|
||
// DisplayUserIDExists 表示短号已被 active 用户占用。
|
||
DisplayUserIDExists Code = "DISPLAY_USER_ID_EXISTS"
|
||
// DisplayUserIDCooldown 表示用户仍处在短号修改冷却期。
|
||
DisplayUserIDCooldown Code = "DISPLAY_USER_ID_COOLDOWN"
|
||
// DisplayUserIDNotFound 表示 active 短号不存在。
|
||
DisplayUserIDNotFound Code = "DISPLAY_USER_ID_NOT_FOUND"
|
||
// DisplayUserIDAllocateFailed 表示默认短号分配重试耗尽。
|
||
DisplayUserIDAllocateFailed Code = "DISPLAY_USER_ID_ALLOCATE_FAILED"
|
||
// DisplayUserIDPrettyNotAvailable 表示靓号不可申请、已被租用或被预留。
|
||
DisplayUserIDPrettyNotAvailable Code = "DISPLAY_USER_ID_PRETTY_NOT_AVAILABLE"
|
||
// DisplayUserIDPrettyActive 表示用户已有 active 靓号租约。
|
||
DisplayUserIDPrettyActive Code = "DISPLAY_USER_ID_PRETTY_ACTIVE"
|
||
// DisplayUserIDLeaseExpired 表示靓号租约已经过期或不能再被当前操作使用。
|
||
DisplayUserIDLeaseExpired Code = "DISPLAY_USER_ID_LEASE_EXPIRED"
|
||
// DisplayUserIDPaymentRequired 表示申请靓号缺少有效支付凭证。
|
||
DisplayUserIDPaymentRequired Code = "DISPLAY_USER_ID_PAYMENT_REQUIRED"
|
||
// CountryChangeCooldown 表示用户仍处在国家修改冷却期。
|
||
CountryChangeCooldown Code = "COUNTRY_CHANGE_COOLDOWN"
|
||
// CountryNotFound 表示国家主数据不存在或已经停用。
|
||
CountryNotFound Code = "COUNTRY_NOT_FOUND"
|
||
// RegionNotFound 表示区域主数据不存在。
|
||
RegionNotFound Code = "REGION_NOT_FOUND"
|
||
// RegionCountryConflict 表示国家已经归属其他 active 区域。
|
||
RegionCountryConflict Code = "REGION_COUNTRY_CONFLICT"
|
||
// RegionDisabled 表示区域已经停用,不能继续配置国家。
|
||
RegionDisabled Code = "REGION_DISABLED"
|
||
// InvalidInviteCode 表示邀请码不存在、停用、跨 App、邀请人不可用或用户尝试自邀。
|
||
InvalidInviteCode Code = "INVALID_INVITE_CODE"
|
||
// DeviceAlreadyRegistered 表示同一注册设备已经创建过账号,不能再次作为三方注册入口。
|
||
DeviceAlreadyRegistered Code = "DEVICE_ALREADY_REGISTERED"
|
||
|
||
// InsufficientBalance 表示钱包余额不足。
|
||
InsufficientBalance Code = "INSUFFICIENT_BALANCE"
|
||
// DuplicateBillingCommand 表示账务幂等键命中已处理命令。
|
||
DuplicateBillingCommand Code = "DUPLICATE_BILLING_COMMAND"
|
||
// LedgerConflict 表示账本并发写入或余额版本冲突。
|
||
LedgerConflict Code = "LEDGER_CONFLICT"
|
||
// CoinSellerNotActive 表示目标用户不存在 active 币商身份。
|
||
CoinSellerNotActive Code = "COIN_SELLER_NOT_ACTIVE"
|
||
// CoinSellerStockTypeInvalid 表示币商库存入账类型不属于支持范围。
|
||
CoinSellerStockTypeInvalid Code = "COIN_SELLER_STOCK_TYPE_INVALID"
|
||
// CoinSellerStockAmountInvalid 表示币商库存金币数或线下付款金额不合法。
|
||
CoinSellerStockAmountInvalid Code = "COIN_SELLER_STOCK_AMOUNT_INVALID"
|
||
// CoinSellerPaymentRefDuplicated 表示同一 USDT 收款凭证已经入账。
|
||
CoinSellerPaymentRefDuplicated Code = "COIN_SELLER_PAYMENT_REF_DUPLICATED"
|
||
// IdempotencyConflict 表示 command_id 相同但业务 payload 不一致。
|
||
IdempotencyConflict Code = "IDEMPOTENCY_CONFLICT"
|
||
// VIPLevelNotFound 表示目标 VIP 等级不存在。
|
||
VIPLevelNotFound Code = "VIP_LEVEL_NOT_FOUND"
|
||
// VIPLevelDisabled 表示目标 VIP 等级当前不可购买。
|
||
VIPLevelDisabled Code = "VIP_LEVEL_DISABLED"
|
||
// VIPDowngradeNotAllowed 表示用户当前有效 VIP 等级高于本次购买目标等级。
|
||
VIPDowngradeNotAllowed Code = "VIP_DOWNGRADE_NOT_ALLOWED"
|
||
// VIPRechargeRequired 表示目标 VIP 等级需要用户先达到累计充值门槛。
|
||
VIPRechargeRequired Code = "VIP_RECHARGE_REQUIRED"
|
||
|
||
// RedPacketDisabled 表示红包功能未启用。
|
||
RedPacketDisabled Code = "RED_PACKET_DISABLED"
|
||
// RedPacketInvalidAmountTier 表示红包金额不在后台配置档位内。
|
||
RedPacketInvalidAmountTier Code = "INVALID_AMOUNT_TIER"
|
||
// RedPacketInvalidCountTier 表示红包数量不在后台配置档位内。
|
||
RedPacketInvalidCountTier Code = "INVALID_COUNT_TIER"
|
||
// RedPacketAmountTooSmall 表示红包总金额小于红包份数,无法保证每份至少 1 金币。
|
||
RedPacketAmountTooSmall Code = "RED_PACKET_AMOUNT_TOO_SMALL"
|
||
// RedPacketDailyLimitReached 表示用户达到 UTC 当日发送次数上限。
|
||
RedPacketDailyLimitReached Code = "DAILY_SEND_LIMIT_REACHED"
|
||
// RedPacketNotOpen 表示延迟红包尚未到打开时间。
|
||
RedPacketNotOpen Code = "RED_PACKET_NOT_OPEN"
|
||
// RedPacketExpired 表示红包已经过期。
|
||
RedPacketExpired Code = "RED_PACKET_EXPIRED"
|
||
// RedPacketAlreadyClaimed 表示当前用户已领取过该红包。
|
||
RedPacketAlreadyClaimed Code = "RED_PACKET_ALREADY_CLAIMED"
|
||
// RedPacketSoldOut 表示红包已经被抢完。
|
||
RedPacketSoldOut Code = "RED_PACKET_SOLD_OUT"
|
||
|
||
// RuleNotActive 表示活动规则当前不可用。
|
||
RuleNotActive Code = "RULE_NOT_ACTIVE"
|
||
// EventAlreadyConsumed 表示活动事件消费幂等命中。
|
||
EventAlreadyConsumed Code = "EVENT_ALREADY_CONSUMED"
|
||
// RewardPending 表示奖励仍在处理中,不能重复结算。
|
||
RewardPending Code = "REWARD_PENDING"
|
||
|
||
// InvalidSection 表示 App 消息分区不属于 system/activity。
|
||
InvalidSection Code = "INVALID_SECTION"
|
||
// MessageNotFound 表示消息不存在、已删除、已过期、已撤回或不属于当前用户。
|
||
MessageNotFound Code = "MESSAGE_NOT_FOUND"
|
||
// MessageRecalled 表示消息已撤回,不能继续展示或操作。
|
||
MessageRecalled Code = "MESSAGE_RECALLED"
|
||
// PageTokenInvalid 表示分页游标不能解析或不属于当前查询。
|
||
PageTokenInvalid Code = "PAGE_TOKEN_INVALID"
|
||
// RequestConflict 表示同 command_id 的 fanout 请求 payload 冲突。
|
||
RequestConflict Code = "REQUEST_CONFLICT"
|
||
// ProducerEventConflict 表示同 producer_event_id 的入箱请求 payload 冲突。
|
||
ProducerEventConflict Code = "PRODUCER_EVENT_CONFLICT"
|
||
)
|
||
|
||
// Error 是 domain/service 层统一返回的业务错误。
|
||
// 该类型不依赖任何传输层包,避免业务代码直接绑定 gRPC status。
|
||
type Error struct {
|
||
Code Code
|
||
Message string
|
||
}
|
||
|
||
// Error 实现标准 error 接口。
|
||
func (e *Error) Error() string {
|
||
return fmt.Sprintf("%s: %s", e.Code, e.Message)
|
||
}
|
||
|
||
// Is 让 errors.Is 可以按稳定 reason 判断业务错误。
|
||
func (e *Error) Is(target error) bool {
|
||
typed, ok := target.(*Error)
|
||
return ok && e.Code == typed.Code
|
||
}
|
||
|
||
// New 构造带稳定 reason 的业务错误。
|
||
func New(code Code, message string) error {
|
||
return &Error{
|
||
Code: code,
|
||
Message: message,
|
||
}
|
||
}
|
||
|
||
// As 提取当前仓库约定的业务错误。
|
||
func As(err error) (*Error, bool) {
|
||
if err == nil {
|
||
return nil, false
|
||
}
|
||
|
||
var typed *Error
|
||
if errors.As(err, &typed) {
|
||
return typed, true
|
||
}
|
||
|
||
return nil, false
|
||
}
|
||
|
||
// CodeOf 安全提取错误 reason。
|
||
func CodeOf(err error) Code {
|
||
if typed, ok := As(err); ok {
|
||
return typed.Code
|
||
}
|
||
|
||
if err == nil {
|
||
return ""
|
||
}
|
||
|
||
return Internal
|
||
}
|
||
|
||
// MessageOf 安全提取可返回给内部调用方的短错误描述。
|
||
func MessageOf(err error) string {
|
||
if typed, ok := As(err); ok {
|
||
return typed.Message
|
||
}
|
||
|
||
if err == nil {
|
||
return ""
|
||
}
|
||
|
||
return "internal error"
|
||
}
|