refactor(*): 平台架构 - 拆分后端领域模块并同步插件协议与模型能力
This commit is contained in:
68
backend/internal/kernel/error_codes.go
Normal file
68
backend/internal/kernel/error_codes.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package kernel
|
||||
|
||||
// 业务信封 code:成功为 0;失败默认等于 HTTP 状态。需要区分同一状态的不同原因时,用 status*100+n。
|
||||
// 不使用与 HTTP 无关的 1001/2001 号段,避免和现有 42901 以及前端按 status/code 判断重试打架。
|
||||
const (
|
||||
CodeOK = 0
|
||||
|
||||
CodeInvalidArgument = 400
|
||||
CodeUnauthorized = 401
|
||||
CodeForbidden = 403
|
||||
CodeNotFound = 404
|
||||
CodeConflict = 409
|
||||
CodeTooManyRequests = 429
|
||||
CodeInternal = 500
|
||||
CodeBadGateway = 502
|
||||
CodeUnavailable = 503
|
||||
CodeTimeout = 504
|
||||
|
||||
CodeQuotaExceeded = 40301
|
||||
CodeIdempotencyConflict = 40901
|
||||
CodeRateLimited = 42901
|
||||
)
|
||||
|
||||
// ErrorReason 是稳定机器可读原因,前端应判断 reason 而不是解析 msg。
|
||||
type ErrorReason string
|
||||
|
||||
const (
|
||||
ReasonInvalidArgument ErrorReason = "invalid_argument"
|
||||
ReasonUnauthorized ErrorReason = "unauthorized"
|
||||
ReasonForbidden ErrorReason = "forbidden"
|
||||
ReasonNotFound ErrorReason = "not_found"
|
||||
ReasonConflict ErrorReason = "conflict"
|
||||
ReasonFailedPrecondition ErrorReason = "failed_precondition"
|
||||
ReasonQuotaExceeded ErrorReason = "quota_exceeded"
|
||||
ReasonRateLimited ErrorReason = "rate_limited"
|
||||
ReasonUnavailable ErrorReason = "unavailable"
|
||||
ReasonTimeout ErrorReason = "timeout"
|
||||
ReasonInternal ErrorReason = "internal"
|
||||
ReasonBadGateway ErrorReason = "bad_gateway"
|
||||
)
|
||||
|
||||
func ReasonForStatus(status int) ErrorReason {
|
||||
switch status {
|
||||
case CodeInvalidArgument:
|
||||
return ReasonInvalidArgument
|
||||
case CodeUnauthorized:
|
||||
return ReasonUnauthorized
|
||||
case CodeForbidden:
|
||||
return ReasonForbidden
|
||||
case CodeNotFound:
|
||||
return ReasonNotFound
|
||||
case CodeConflict:
|
||||
return ReasonConflict
|
||||
case CodeTooManyRequests:
|
||||
return ReasonRateLimited
|
||||
case CodeBadGateway:
|
||||
return ReasonBadGateway
|
||||
case CodeUnavailable:
|
||||
return ReasonUnavailable
|
||||
case CodeTimeout:
|
||||
return ReasonTimeout
|
||||
default:
|
||||
if status >= 500 {
|
||||
return ReasonInternal
|
||||
}
|
||||
return ReasonInvalidArgument
|
||||
}
|
||||
}
|
||||
60
backend/internal/kernel/errors.go
Normal file
60
backend/internal/kernel/errors.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package kernel
|
||||
|
||||
// AppError 是业务层对外公开的结构化错误。
|
||||
// Message 必须可安全展示给用户,Cause 仅用于保留内部诊断链路,不得直接写入 HTTP 响应。
|
||||
type AppError struct {
|
||||
Status int
|
||||
Code int
|
||||
Reason ErrorReason
|
||||
Message string
|
||||
Retryable bool
|
||||
Cause error
|
||||
}
|
||||
|
||||
func (e *AppError) Error() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func (e *AppError) Unwrap() error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.Cause
|
||||
}
|
||||
|
||||
func NewAppError(status int, message string) *AppError {
|
||||
return &AppError{Status: status, Code: status, Reason: ReasonForStatus(status), Message: message}
|
||||
}
|
||||
|
||||
func WrapAppError(status int, message string, cause error) *AppError {
|
||||
err := NewAppError(status, message)
|
||||
err.Cause = cause
|
||||
return err
|
||||
}
|
||||
|
||||
func RateLimited(message string) *AppError {
|
||||
return &AppError{Status: CodeTooManyRequests, Code: CodeRateLimited, Reason: ReasonRateLimited, Message: message, Retryable: true}
|
||||
}
|
||||
|
||||
func QuotaExceeded(message string) *AppError {
|
||||
return &AppError{Status: CodeForbidden, Code: CodeQuotaExceeded, Reason: ReasonQuotaExceeded, Message: message}
|
||||
}
|
||||
|
||||
func BadAuthRequest(message string) *AppError {
|
||||
return NewAppError(400, message)
|
||||
}
|
||||
|
||||
func NotFound(message string) *AppError {
|
||||
return NewAppError(404, message)
|
||||
}
|
||||
|
||||
func Unauthorized(message string) *AppError {
|
||||
return NewAppError(401, message)
|
||||
}
|
||||
|
||||
func Forbidden(message string) *AppError {
|
||||
return NewAppError(403, message)
|
||||
}
|
||||
82
backend/internal/kernel/util.go
Normal file
82
backend/internal/kernel/util.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewID 生成唯一标识符。
|
||||
func NewID() string {
|
||||
var b [16]byte
|
||||
if _, err := rand.Read(b[:]); err != nil {
|
||||
return fmt.Sprintf("%d", time.Now().UnixNano())
|
||||
}
|
||||
return hex.EncodeToString(b[:])
|
||||
}
|
||||
|
||||
// FirstNonEmpty 返回第一个非空字符串。
|
||||
func FirstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// UniqueNonEmpty 去重并过滤空字符串。
|
||||
func UniqueNonEmpty(values []string) []string {
|
||||
seen := map[string]bool{}
|
||||
result := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || seen[value] {
|
||||
continue
|
||||
}
|
||||
seen[value] = true
|
||||
result = append(result, value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// DefaultString 返回非空值或回退值。
|
||||
func DefaultString(value string, fallback string) string {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Ptr 返回值的指针。
|
||||
func Ptr[T any](value T) *T {
|
||||
return &value
|
||||
}
|
||||
|
||||
// StringValue 从任意值提取字符串。
|
||||
func StringValue(value any) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
if text, ok := value.(string); ok {
|
||||
return text
|
||||
}
|
||||
return fmt.Sprintf("%v", value)
|
||||
}
|
||||
|
||||
// TruncateRunes 截断字符串到指定 rune 数。
|
||||
func TruncateRunes(value string, limit int) string {
|
||||
runes := []rune(value)
|
||||
if len(runes) <= limit {
|
||||
return value
|
||||
}
|
||||
return string(runes[:limit]) + "..."
|
||||
}
|
||||
|
||||
// Megabytes 转换 MB 到字节。
|
||||
func Megabytes(value int64) int64 { return value << 20 }
|
||||
|
||||
// Gigabytes 转换 GB 到字节。
|
||||
func Gigabytes(value int64) int64 { return value << 30 }
|
||||
Reference in New Issue
Block a user