Files
2026-01-16 15:49:34 +08:00

318 lines
16 KiB
Go

package errors
import (
stderrors "errors"
"fmt"
"strings"
"goravel/app/http/trans"
"github.com/goravel/framework/contracts/http"
)
// 定义业务错误类型
var (
// 认证相关错误
ErrAccountDisabled = NewBusinessError("account_disabled", "账号已被禁用")
ErrPasswordError = NewBusinessError("password_error", "密码错误")
ErrNotLoggedIn = NewBusinessError("not_logged_in", "未登录")
ErrUsernameOrPasswordErr = NewBusinessError("username_or_password_error", "用户名或密码错误")
ErrLoginFailed = NewBusinessError("login_failed", "登录失败")
// 验证相关错误
ErrValidationFailed = NewBusinessError("validation_failed", "验证失败")
ErrInvalidArgument = NewBusinessError("invalid_argument", "无效的参数")
// 资源相关错误
ErrRecordNotFound = NewBusinessError("record_not_found", "记录不存在")
ErrBlacklistNotFound = NewBusinessError("blacklist_not_found", "黑名单不存在")
ErrNotificationNotFound = NewBusinessError("notification_not_found", "通知不存在")
ErrAdminNotFound = NewBusinessError("admin_not_found", "管理员不存在")
ErrRoleNotFound = NewBusinessError("role_not_found", "角色不存在")
ErrMenuNotFound = NewBusinessError("menu_not_found", "菜单不存在")
ErrPermissionNotFound = NewBusinessError("permission_not_found", "权限不存在")
ErrDepartmentNotFound = NewBusinessError("department_not_found", "部门不存在")
ErrDictionaryNotFound = NewBusinessError("dictionary_not_found", "字典不存在")
ErrLogNotFound = NewBusinessError("log_not_found", "日志不存在")
// IP 相关错误
ErrIPAddressRequired = NewBusinessError("ip_address_required", "IP地址不能为空")
ErrInvalidCIDRFormat = NewBusinessError("invalid_cidr_format", "CIDR格式错误")
ErrInvalidIPRangeFormat = NewBusinessError("invalid_ip_range_format", "IP范围格式错误")
ErrInvalidIPFormat = NewBusinessError("invalid_ip_format", "IP格式错误")
ErrInvalidIPRangeOrder = NewBusinessError("invalid_ip_range_order", "IP范围顺序错误")
// 参数相关错误
ErrIDRequired = NewBusinessError("id_required", "ID不能为空")
ErrIDsRequired = NewBusinessError("ids_required", "IDs不能为空")
ErrParamsError = NewBusinessError("params_error", "参数错误")
ErrParamsRequired = NewBusinessError("params_required", "参数不能为空")
ErrFileRequired = NewBusinessError("file_required", "文件不能为空")
ErrFilePathRequired = NewBusinessError("file_path_required", "文件路径不能为空")
ErrCodeRequired = NewBusinessError("code_required", "验证码不能为空")
ErrTokenIDRequired = NewBusinessError("token_id_required", "Token ID不能为空")
ErrTokenIDsRequired = NewBusinessError("token_ids_required", "Token IDs不能为空")
ErrUserIDRequired = NewBusinessError("user_id_required", "用户ID不能为空")
ErrChunkIDRequired = NewBusinessError("chunk_id_required", "分片ID不能为空")
ErrFilenameRequired = NewBusinessError("filename_required", "文件名不能为空")
// 附件相关错误
ErrChunkUploadOnlyLocalStorage = NewBusinessError("chunk_upload_only_local_storage", "大文件分片上传仅支持本地存储")
ErrInvalidChunkIndex = NewBusinessError("invalid_chunk_index", "分片索引无效")
ErrInvalidTotalChunks = NewBusinessError("invalid_total_chunks", "总分片数无效")
ErrInvalidTotalSize = NewBusinessError("invalid_total_size", "总大小无效")
ErrInvalidChunkSize = NewBusinessError("invalid_chunk_size", "分片大小无效")
ErrChunkFileRequired = NewBusinessError("chunk_file_required", "分片文件不能为空")
ErrInvalidAction = NewBusinessError("invalid_action", "无效的操作")
ErrAttachmentNotFound = NewBusinessError("attachment_not_found", "附件不存在")
ErrChunkNotFound = NewBusinessError("chunk_not_found", "分片不存在")
ErrChunkMissing = NewBusinessError("chunk_missing", "分片缺失")
ErrNoChunkDataToMerge = NewBusinessError("no_chunk_data_to_merge", "没有可合并的分片数据")
ErrSaveChunkFailed = NewBusinessError("save_chunk_failed", "保存分片失败")
ErrCreateDirectoryFailed = NewBusinessError("create_directory_failed", "创建目标目录失败")
ErrCreateFileFailed = NewBusinessError("create_file_failed", "创建目标文件失败")
ErrWriteChunkFailed = NewBusinessError("write_chunk_failed", "写入分片失败")
ErrCloseFileFailed = NewBusinessError("close_file_failed", "关闭目标文件失败")
ErrSaveFileFailed = NewBusinessError("save_file_failed", "保存文件失败")
ErrDeleteFileFailed = NewBusinessError("delete_file_failed", "删除文件失败")
// 数据存在性错误
ErrUsernameExists = NewBusinessError("username_exists", "用户名已存在")
ErrMenuSlugExists = NewBusinessError("menu_slug_exists", "菜单标识已存在")
ErrRoleNameExists = NewBusinessError("role_name_exists", "角色名称已存在")
ErrRoleSlugExists = NewBusinessError("role_slug_exists", "角色标识已存在")
ErrPermissionNameExists = NewBusinessError("permission_name_exists", "权限名称已存在")
ErrPermissionSlugExists = NewBusinessError("permission_slug_exists", "权限标识已存在")
ErrPermissionNameOrSlugExists = NewBusinessError("permission_name_or_slug_exists", "权限名称或标识已存在")
ErrPermissionNameAndSlugRequired = NewBusinessError("permission_name_and_slug_required", "权限名称和标识不能为空")
// 业务逻辑错误
ErrMenuHasChildren = NewBusinessError("menu_has_children", "菜单存在子菜单,无法删除")
ErrDepartmentHasChildren = NewBusinessError("department_has_children", "部门存在子部门,无法删除")
ErrDepartmentHasAdmins = NewBusinessError("department_has_admins", "部门存在管理员,无法删除")
ErrGoogleAuthenticatorNotBound = NewBusinessError("google_authenticator_not_bound", "未绑定谷歌验证器")
ErrGoogleAuthenticatorAlreadyBound = NewBusinessError("google_authenticator_already_bound", "已绑定谷歌验证器")
ErrGoogleCodeInvalid = NewBusinessError("google_code_invalid", "谷歌验证码无效")
ErrGoogleCodeRequired = NewBusinessError("google_code_required", "谷歌验证码不能为空")
ErrSecretAndCodeRequired = NewBusinessError("secret_and_code_required", "密钥和验证码不能为空")
ErrOldPasswordError = NewBusinessError("old_password_error", "旧密码错误")
ErrInvalidTokenID = NewBusinessError("invalid_token_id", "无效的Token ID")
ErrInvalidTokenIDs = NewBusinessError("invalid_token_ids", "无效的Token IDs")
ErrInvalidUserID = NewBusinessError("invalid_user_id", "无效的用户ID")
ErrDictionaryTypeRequired = NewBusinessError("dictionary_type_required", "字典类型不能为空")
ErrConfigGroupRequired = NewBusinessError("config_group_required", "配置组不能为空")
ErrConfigsRequired = NewBusinessError("configs_required", "配置不能为空")
ErrEmailConfigRequired = NewBusinessError("email_config_required", "邮箱配置不能为空")
ErrOptionTypeRequired = NewBusinessError("option_type_required", "选项类型不能为空")
ErrInvalidOptionType = NewBusinessError("invalid_option_type", "无效的选项类型")
// 余额相关错误
ErrInsufficientBalance = NewBusinessError("insufficient_balance", "余额不足")
ErrInvalidBalanceType = NewBusinessError("invalid_balance_type", "无效的变动类型")
// 订单相关错误
ErrOrderNotFound = NewBusinessError("order_not_found", "订单不存在")
ErrOrderIDRequired = NewBusinessError("order_id_required", "订单ID不能为空")
ErrGetLockFailed = NewBusinessError("get_lock_failed", "获取锁失败")
ErrGenerateOrderNoFailed = NewBusinessError("generate_order_no_failed", "生成唯一订单号失败,请重试")
ErrCreateOrderFailed = NewBusinessError("create_order_failed", "创建订单失败")
ErrCreateOrderDetailFailed = NewBusinessError("create_order_detail_failed", "创建订单详情失败")
ErrQueryOrderDetailFailed = NewBusinessError("query_order_detail_failed", "查询订单详情失败")
ErrDeleteOrderDetailFailed = NewBusinessError("delete_order_detail_failed", "删除订单详情失败")
// 支付相关错误
ErrPaymentMethodNotFound = NewBusinessError("payment_method_not_found", "支付方式不存在")
ErrPaymentNotFound = NewBusinessError("payment_not_found", "支付记录不存在")
ErrPaymentMethodDisabled = NewBusinessError("payment_method_disabled", "支付方式已禁用")
ErrPaymentMethodCodeExists = NewBusinessError("payment_method_code_exists", "支付方式代码已存在")
ErrInvalidPaymentType = NewBusinessError("invalid_payment_type", "无效的支付类型")
ErrPaymentConfigRequired = NewBusinessError("payment_config_required", "支付配置不能为空")
ErrCreatePaymentFailed = NewBusinessError("create_payment_failed", "创建支付记录失败")
ErrPaymentAmountInvalid = NewBusinessError("payment_amount_invalid", "支付金额无效")
ErrPaymentStatusInvalid = NewBusinessError("payment_status_invalid", "支付状态无效")
// 导出相关错误
ErrExportRecordNotFound = NewBusinessError("export_record_not_found", "导出记录不存在")
ErrWriteCSVHeaderFailed = NewBusinessError("write_csv_header_failed", "写入CSV表头失败")
ErrWriteCSVDataFailed = NewBusinessError("write_csv_data_failed", "写入CSV数据失败")
ErrCSVWriteFailed = NewBusinessError("csv_write_failed", "CSV写入失败")
ErrExcelNotImplemented = NewBusinessError("excel_not_implemented", "Excel导出功能暂未实现,请使用CSV格式")
ErrBatchDeleteExportFailed = NewBusinessError("batch_delete_export_failed", "批量删除导出记录失败")
// 导入相关错误
ErrInvalidCSVFormat = NewBusinessError("invalid_csv_format", "CSV格式无效")
ErrInvalidFileType = NewBusinessError("invalid_file_type", "文件类型无效")
// 分表相关错误
ErrBaseTableNotRegistered = NewBusinessError("base_table_not_registered", "未注册的基础表名")
ErrCreateShardingTableFailed = NewBusinessError("create_sharding_table_failed", "创建分表失败")
// 操作相关错误
ErrCreateFailed = NewBusinessError("create_failed", "创建失败")
ErrUpdateFailed = NewBusinessError("update_failed", "更新失败")
ErrQueryFailed = NewBusinessError("query_failed", "查询失败")
ErrDeleteFailed = NewBusinessError("delete_failed", "删除失败")
ErrPasswordEncryptFailed = NewBusinessError("password_encrypt_failed", "密码加密失败")
// 资源不存在错误(其他资源错误已在资源相关错误部分定义)
ErrTokenNotFound = NewBusinessError("token_not_found", "Token不存在")
ErrUnauthorized = NewBusinessError("unauthorized", "未授权")
ErrTokenRefreshFailed = NewBusinessError("token_refresh_failed", "Token刷新失败")
ErrUserNotFound = NewBusinessError("user_not_found", "用户不存在")
// 权限保护错误
ErrAdminProtectedCannotDisable = NewBusinessError("admin_protected_cannot_disable", "受保护的管理员不能禁用")
ErrAdminCannotModifyRoles = NewBusinessError("admin_cannot_modify_roles", "不能修改管理员角色")
ErrAdminProtectedCannotDelete = NewBusinessError("admin_protected_cannot_delete", "受保护的管理员不能删除")
ErrAdminCannotDeleteSelf = NewBusinessError("admin_cannot_delete_self", "不能删除自己")
ErrRoleProtectedCannotModifySlug = NewBusinessError("role_protected_cannot_modify_slug", "受保护的角色不能修改标识")
ErrRoleProtectedCannotDisable = NewBusinessError("role_protected_cannot_disable", "受保护的角色不能禁用")
ErrRoleProtectedCannotDelete = NewBusinessError("role_protected_cannot_delete", "受保护的角色不能删除")
// 测试相关错误
ErrTraceTestWarning = NewBusinessError("trace_test_warning", "追踪测试警告")
ErrTraceTestError = NewBusinessError("trace_test_error", "追踪测试错误")
)
// BusinessError 业务错误类型
type BusinessError struct {
Code string
Message string
Err error
Params map[string]any // 用于存储动态参数(如余额值)
}
// NewBusinessError 创建新的业务错误
func NewBusinessError(code, message string) *BusinessError {
return &BusinessError{
Code: code,
Message: message,
}
}
// Error 实现 error 接口
func (e *BusinessError) Error() string {
if e.Err != nil {
return fmt.Sprintf("[%s] %s: %v", e.Code, e.Message, e.Err)
}
return fmt.Sprintf("[%s] %s", e.Code, e.Message)
}
// Unwrap 返回包装的错误
func (e *BusinessError) Unwrap() error {
return e.Err
}
// WithError 包装底层错误
func (e *BusinessError) WithError(err error) *BusinessError {
e.Err = err
return e
}
// WithMessage 设置自定义消息
func (e *BusinessError) WithMessage(message string) *BusinessError {
e.Message = message
return e
}
// WithParams 设置动态参数
// 参数会在控制器中用于替换翻译后消息中的占位符
// 例如:翻译文件中有 "insufficient_balance": "余额不足,当前余额: {balance}"
// 使用 WithParams(map[string]any{"balance": 100}) 后,控制器会替换为 "余额不足,当前余额: 100.00"
func (e *BusinessError) WithParams(params map[string]any) *BusinessError {
if e.Params == nil {
e.Params = make(map[string]any)
}
for k, v := range params {
e.Params[k] = v
}
return e
}
// Is 检查错误是否匹配
func (e *BusinessError) Is(target error) bool {
if t, ok := target.(*BusinessError); ok {
return e.Code == t.Code
}
return stderrors.Is(e.Err, target)
}
// WrapError 包装错误并添加上下文信息
func WrapError(err error, code, message string) *BusinessError {
return &BusinessError{
Code: code,
Message: message,
Err: err,
}
}
// IsBusinessError 检查是否是业务错误
func IsBusinessError(err error) bool {
_, ok := err.(*BusinessError)
return ok
}
// GetBusinessError 获取业务错误
func GetBusinessError(err error) (*BusinessError, bool) {
be, ok := err.(*BusinessError)
return be, ok
}
// GetFormattedMessage 获取格式化后的消息(支持多语言和占位符替换)
// 自动处理翻译和占位符替换,简化控制器代码
// 使用示例:
//
// message := businessErr.GetFormattedMessage(ctx)
func (e *BusinessError) GetFormattedMessage(ctx http.Context) string {
// 1. 获取翻译后的消息
message := trans.Get(ctx, e.Code)
// 2. 如果翻译不存在(返回的是 key),使用默认消息
if message == e.Code {
message = e.Message
}
// 3. 如果有参数,替换占位符
if len(e.Params) > 0 {
message = e.replacePlaceholders(message)
}
return message
}
// replacePlaceholders 替换消息中的占位符
// 支持 {key} 和 ${key} 格式
func (e *BusinessError) replacePlaceholders(message string) string {
result := message
for key, value := range e.Params {
// 支持 {key} 和 ${key} 格式
placeholder1 := fmt.Sprintf("{%s}", key)
placeholder2 := fmt.Sprintf("${%s}", key)
// 格式化值
valueStr := e.formatValue(value)
// 替换占位符
result = strings.ReplaceAll(result, placeholder1, valueStr)
result = strings.ReplaceAll(result, placeholder2, valueStr)
}
return result
}
// formatValue 格式化参数值
// float64/float32 保留2位小数,整数保持原样,其他类型使用 %v
func (e *BusinessError) formatValue(value any) string {
switch v := value.(type) {
case float64:
return fmt.Sprintf("%.2f", v)
case float32:
return fmt.Sprintf("%.2f", v)
case int, int8, int16, int32, int64:
return fmt.Sprintf("%d", v)
case uint, uint8, uint16, uint32, uint64:
return fmt.Sprintf("%d", v)
default:
return fmt.Sprintf("%v", v)
}
}