From b890d8172b47280b9c860ba4965446ef550836de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=B9=BF?= Date: Mon, 7 Jul 2025 17:57:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=A4=84=E7=90=86=E6=9C=BA=E5=88=B6=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E4=BB=A3=E7=A0=81=E5=92=8C=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E5=8C=96=E9=94=99=E8=AF=AF=E5=8C=85=E8=A3=85=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/config/config.go | 42 +++++++++++++--- internal/errors/codes.go | 33 +++++++++++++ internal/errors/error.go | 68 ++++++++++++++++++++++++++ internal/errors/error_test.go | 91 +++++++++++++++++++++++++++++++++++ internal/errors/helpers.go | 72 +++++++++++++++++++++++++++ internal/logger/fields.go | 9 ++++ 6 files changed, 309 insertions(+), 6 deletions(-) create mode 100644 internal/errors/codes.go create mode 100644 internal/errors/error.go create mode 100644 internal/errors/error_test.go create mode 100644 internal/errors/helpers.go create mode 100644 internal/logger/fields.go diff --git a/internal/config/config.go b/internal/config/config.go index 86b4ab5..be6ee95 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,10 +2,10 @@ package config import ( - "fmt" "os" "path/filepath" + "git.kingecg.top/kingecg/installerbuilder/internal/errors" "github.com/spf13/viper" "gopkg.in/yaml.v3" ) @@ -157,12 +157,24 @@ func LoadConfig(path string) (*Config, error) { v.SetConfigFile(path) if err := v.ReadInConfig(); err != nil { - return nil, fmt.Errorf("读取配置文件失败: %w", err) + return nil, errors.WrapWithCode( + err, + errors.ErrConfigParse, + "ConfigLoader", + "读取配置文件失败", + map[string]interface{}{"path": path}, + ) } var config Config if err := v.Unmarshal(&config); err != nil { - return nil, fmt.Errorf("解析配置文件失败: %w", err) + return nil, errors.WrapWithCode( + err, + errors.ErrConfigParse, + "ConfigLoader", + "解析配置文件失败", + map[string]interface{}{"path": path}, + ) } return &config, nil @@ -173,18 +185,36 @@ func SaveConfig(config *Config, path string) error { // 确保目录存在 dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0755); err != nil { - return fmt.Errorf("创建目录失败: %w", err) + return errors.WrapWithCode( + err, + errors.ErrResourceCopy, + "ConfigSaver", + "创建目录失败", + map[string]interface{}{"path": dir}, + ) } // 将配置转换为YAML data, err := yaml.Marshal(config) if err != nil { - return fmt.Errorf("序列化配置失败: %w", err) + return errors.WrapWithCode( + err, + errors.ErrConfigParse, + "ConfigSaver", + "序列化配置失败", + nil, + ) } // 写入文件 if err := os.WriteFile(path, data, 0644); err != nil { - return fmt.Errorf("写入配置文件失败: %w", err) + return errors.WrapWithCode( + err, + errors.ErrResourceCopy, + "ConfigSaver", + "写入配置文件失败", + map[string]interface{}{"path": path}, + ) } return nil diff --git a/internal/errors/codes.go b/internal/errors/codes.go new file mode 100644 index 0000000..d85586b --- /dev/null +++ b/internal/errors/codes.go @@ -0,0 +1,33 @@ +// Package errors 提供错误处理框架 +package errors + +// 错误代码常量 +const ( + // 配置错误 + ErrConfigParse = "CONFIG_PARSE_ERROR" // 配置解析错误 + ErrConfigValidation = "CONFIG_VALIDATION_ERROR" // 配置验证错误 + + // 构建错误 + ErrBuildFailed = "BUILD_FAILED" // 构建失败 + ErrBuildEnvironment = "BUILD_ENVIRONMENT_ERROR" // 构建环境错误 + + // 平台错误 + ErrPlatformNotSupported = "PLATFORM_NOT_SUPPORTED" // 平台不支持 + ErrArchNotSupported = "ARCH_NOT_SUPPORTED" // 架构不支持 + + // 包构建错误 + ErrPackageTypeFailed = "PACKAGE_TYPE_FAILED" // 包类型构建失败 + ErrPackageToolNotFound = "PACKAGE_TOOL_NOT_FOUND" // 包构建工具未找到 + + // 脚本错误 + ErrScriptExecution = "SCRIPT_EXECUTION_ERROR" // 脚本执行错误 + ErrScriptNotFound = "SCRIPT_NOT_FOUND" // 脚本未找到 + + // 插件错误 + ErrPluginLoad = "PLUGIN_LOAD_ERROR" // 插件加载错误 + ErrPluginInit = "PLUGIN_INIT_ERROR" // 插件初始化错误 + + // 资源错误 + ErrResourceNotFound = "RESOURCE_NOT_FOUND" // 资源未找到 + ErrResourceCopy = "RESOURCE_COPY_ERROR" // 资源复制错误 +) diff --git a/internal/errors/error.go b/internal/errors/error.go new file mode 100644 index 0000000..a4f8bbb --- /dev/null +++ b/internal/errors/error.go @@ -0,0 +1,68 @@ +// Package errors 提供错误处理框架 +package errors + +import ( + "fmt" +) + +// Error 表示构建过程中的错误 +type Error interface { + error + // Code 返回错误代码 + Code() string + // Component 返回发生错误的组件 + Component() string + // Details 返回详细错误信息 + Details() map[string]interface{} +} + +// BuildError 实现了Error接口 +type BuildError struct { + code string + component string + message string + details map[string]interface{} + cause error +} + +// NewBuildError 创建一个新的BuildError +func NewBuildError(code, component, message string, cause error, details map[string]interface{}) *BuildError { + if details == nil { + details = make(map[string]interface{}) + } + return &BuildError{ + code: code, + component: component, + message: message, + details: details, + cause: cause, + } +} + +// Error 实现error接口 +func (e *BuildError) Error() string { + if e.cause != nil { + return fmt.Sprintf("[%s] %s: %s (caused by: %v)", e.code, e.component, e.message, e.cause) + } + return fmt.Sprintf("[%s] %s: %s", e.code, e.component, e.message) +} + +// Code 返回错误代码 +func (e *BuildError) Code() string { + return e.code +} + +// Component 返回发生错误的组件 +func (e *BuildError) Component() string { + return e.component +} + +// Details 返回详细错误信息 +func (e *BuildError) Details() map[string]interface{} { + return e.details +} + +// Unwrap 返回底层错误 +func (e *BuildError) Unwrap() error { + return e.cause +} diff --git a/internal/errors/error_test.go b/internal/errors/error_test.go new file mode 100644 index 0000000..f850c89 --- /dev/null +++ b/internal/errors/error_test.go @@ -0,0 +1,91 @@ +// Package errors 提供错误处理框架 +package errors + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBuildError_Error(t *testing.T) { + // 测试没有底层错误的情况 + err := NewBuildError( + ErrConfigParse, + "ConfigParser", + "Failed to parse config file", + nil, + map[string]interface{}{"file": "config.yaml"}, + ) + + assert.Equal(t, "[CONFIG_PARSE_ERROR] ConfigParser: Failed to parse config file", err.Error()) + assert.Equal(t, ErrConfigParse, err.Code()) + assert.Equal(t, "ConfigParser", err.Component()) + assert.Equal(t, "config.yaml", err.Details()["file"]) + + // 测试有底层错误的情况 + cause := errors.New("file not found") + err = NewBuildError( + ErrConfigParse, + "ConfigParser", + "Failed to parse config file", + cause, + map[string]interface{}{"file": "config.yaml"}, + ) + + assert.Contains(t, err.Error(), "caused by: file not found") + assert.Equal(t, cause, err.Unwrap()) +} + +func TestWrap(t *testing.T) { + cause := errors.New("original error") + wrapped := Wrap(cause, "context message") + + assert.Contains(t, wrapped.Error(), "context message") + assert.Contains(t, wrapped.Error(), "original error") + assert.True(t, errors.Is(wrapped, cause)) +} + +func TestWrapWithCode(t *testing.T) { + cause := errors.New("original error") + wrapped := WrapWithCode( + cause, + ErrConfigParse, + "ConfigParser", + "Failed to parse config", + map[string]interface{}{"file": "config.yaml"}, + ) + + assert.Equal(t, ErrConfigParse, wrapped.Code()) + assert.Equal(t, "ConfigParser", wrapped.Component()) + assert.Contains(t, wrapped.Error(), "Failed to parse config") + assert.Equal(t, "config.yaml", wrapped.Details()["file"]) +} + +func TestIsCode(t *testing.T) { + err := NewBuildError( + ErrConfigParse, + "ConfigParser", + "Failed to parse config file", + nil, + nil, + ) + + assert.True(t, IsCode(err, ErrConfigParse)) + assert.False(t, IsCode(err, ErrBuildFailed)) + assert.False(t, IsCode(errors.New("regular error"), ErrConfigParse)) +} + +func TestFormatErrorList(t *testing.T) { + errs := []error{ + errors.New("error 1"), + errors.New("error 2"), + errors.New("error 3"), + } + + formatted := FormatErrorList(errs) + assert.Contains(t, formatted, "error 1") + assert.Contains(t, formatted, "error 2") + assert.Contains(t, formatted, "error 3") + assert.Contains(t, formatted, "\n") +} diff --git a/internal/errors/helpers.go b/internal/errors/helpers.go new file mode 100644 index 0000000..101411b --- /dev/null +++ b/internal/errors/helpers.go @@ -0,0 +1,72 @@ +// Package errors 提供错误处理框架 +package errors + +import ( + "errors" + "fmt" + "strings" + + "git.kingecg.top/kingecg/installerbuilder/internal/logger" +) + +// Wrap 包装错误,添加上下文信息 +func Wrap(err error, message string) error { + if err == nil { + return nil + } + return fmt.Errorf("%s: %w", message, err) +} + +// WrapWithCode 包装错误,添加错误代码和组件信息 +func WrapWithCode(err error, code, component, message string, details map[string]interface{}) Error { + if err == nil { + return nil + } + return NewBuildError(code, component, message, err, details) +} + +// IsCode 检查错误是否具有特定的错误代码 +func IsCode(err error, code string) bool { + var buildErr *BuildError + if errors.As(err, &buildErr) { + return buildErr.Code() == code + } + return false +} + +// LogError 记录错误并返回原始错误 +func LogError(err error, component string) error { + if err == nil { + return nil + } + + var buildErr *BuildError + if errors.As(err, &buildErr) { + fields := logger.Fields{ + "code": buildErr.Code(), + "component": buildErr.Component(), + } + for k, v := range buildErr.Details() { + fields[k] = v + } + logger.WithFields(fields).Error(buildErr.Error()) + } else { + logger.WithField("component", component).Error(err.Error()) + } + + return err +} + +// FormatErrorList 格式化错误列表为字符串 +func FormatErrorList(errors []error) string { + if len(errors) == 0 { + return "" + } + + messages := make([]string, len(errors)) + for i, err := range errors { + messages[i] = err.Error() + } + + return strings.Join(messages, "\n") +} diff --git a/internal/logger/fields.go b/internal/logger/fields.go new file mode 100644 index 0000000..10e4d88 --- /dev/null +++ b/internal/logger/fields.go @@ -0,0 +1,9 @@ +// Package logger 提供日志记录功能 +package logger + +import ( + "github.com/sirupsen/logrus" +) + +// Fields 类型是一个用于传递给日志函数的字段集合 +type Fields logrus.Fields