Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions dbm-services/k8s-dbs/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"k8s-dbs/core"
_ "k8s-dbs/core/checker/addonoperation"
"k8s-dbs/core/util"
"k8s-dbs/i18n"
dbsinformer "k8s-dbs/informers"
"k8s-dbs/router"
_ "k8s-dbs/router/core"
Expand Down Expand Up @@ -58,6 +59,12 @@ func main() {
}
slog.Info("Finish initial configuration...")

// 初始化 i18n 国际化
if err := i18n.Init(); err != nil {
log.Fatalf("Failed to initialize i18n: %v", err)
}
slog.Info("Finish initial i18n...")

engine := gin.Default()
// 注册中间件
middleHelper.RegisterMiddleWare(engine)
Expand Down
24 changes: 20 additions & 4 deletions dbm-services/k8s-dbs/common/api/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
"fmt"
commvalidator "k8s-dbs/common/validator"
dbserrors "k8s-dbs/errors"
"k8s-dbs/i18n"
i18nctx "k8s-dbs/i18n/context"
"net/http"

"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -62,19 +64,33 @@ func ErrorResponse(ctx *gin.Context, err error) {
var code ResponseCode
var dbsError = new(dbserrors.K8sDbsError)
var message string
var errorDetail string

// 获取 localizer
localizer := i18nctx.GetLanguage(ctx)

if errors.As(err, &dbsError) {
code = ResponseCode(dbsError.Code)
message = dbsError.Message
// 本地化错误消息
localizedErr := dbsError.Localize(localizer)

code = ResponseCode(localizedErr.Code)
message = localizedErr.Message
errorDetail = localizedErr.ErrorDetail
} else {
code = ResponseCode(500)
message = err.Error()
errorDetail = err.Error()
}

// 使用 i18n 翻译分隔符
separator := i18n.Translate(localizer, i18n.MsgSeparator)

resp := &Response{
Result: false,
Code: code,
Data: nil,
Message: fmt.Sprintf("%s。%s", message, dbsError.ErrorDetail),
Error: dbsError.ErrorDetail,
Message: fmt.Sprintf("%s%s%s", message, separator, errorDetail),
Error: errorDetail,
}
ctx.JSON(http.StatusOK, resp)
response, _ := json.Marshal(resp)
Expand Down
181 changes: 123 additions & 58 deletions dbm-services/k8s-dbs/errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,38 @@ limitations under the License.

package errors

import (
"k8s-dbs/i18n"

goi18n "github.com/nicksnyder/go-i18n/v2/i18n"
)

// K8sDbsError Error
type K8sDbsError struct {
Code ErrorCode `json:"code"` // Service Code
Message string `json:"message"` // Text information corresponding to the src code
ErrorDetail string `json:"errorDetail"`
Code ErrorCode `json:"code"` // Service Code
MessageKey string `json:"-"` // i18n message key (not serialized)
Message string `json:"message"` // Text information corresponding to the src code
ErrorDetail string `json:"errorDetail"` // Detailed error message
}

// Error string of error
func (e *K8sDbsError) Error() string {
return e.ErrorDetail
}

// Localize 使用 localizer 本地化错误消息
func (e *K8sDbsError) Localize(localizer *goi18n.Localizer) *K8sDbsError {
if localizer == nil || e.MessageKey == "" {
return e
}
return &K8sDbsError{
Code: e.Code,
MessageKey: e.MessageKey,
Message: i18n.Translate(localizer, e.MessageKey),
ErrorDetail: e.ErrorDetail,
}
}

type ErrorCode int

// 通用内部业务逻辑异常
Expand Down Expand Up @@ -103,81 +123,126 @@ const (
UpgradeAddonError
)

// 定义错误码对于的message
var codeTag = map[ErrorCode]string{
// errorCodeInfo 错误码信息结构体
type errorCodeInfo struct {
MessageKey string // i18n 消息 key
DefaultMessage string // 默认消息(中文)
}

// codeInfoMap 错误码到 i18n key 和默认消息的映射
var codeInfoMap = map[ErrorCode]errorCodeInfo{
// 纳管系统内置异常
AuthError: "权限不足,请联系管理员",
ServerError: "内部服务器出现错误",
EngineTypeError: "数据库引擎类型有误",
AuthorizationError: "签名信息有误",
ThirdAPIError: "调用第三方 API 接口失败",
ResubmitError: "请勿重复提交",
LoginError: "登录失败",
LogoutError: "注销失败",
CreateMetaDataError: "创建元数据失败",
UpdateMetaDataError: "更新元数据失败",
GetMetaDataError: "获取元数据失败",
DeleteMetaDataError: "删除元数据失败",
ParameterInvalidError: "参数校验失败",
ParameterTypeError: "参数类型校验失败",
ParameterValueError: "参数值校验失败",
OperationForbidden: "禁止执行该操作",
AuthError: {i18n.MsgErrAuth, "权限不足,请联系管理员"},
ServerError: {i18n.MsgErrServer, "内部服务器出现错误"},
EngineTypeError: {i18n.MsgErrEngineType, "数据库引擎类型有误"},
AuthorizationError: {i18n.MsgErrAuthorization, "签名信息有误"},
ThirdAPIError: {i18n.MsgErrThirdAPI, "调用第三方 API 接口失败"},
ResubmitError: {i18n.MsgErrResubmit, "请勿重复提交"},
LoginError: {i18n.MsgErrLogin, "登录失败"},
LogoutError: {i18n.MsgErrLogout, "注销失败"},
CreateMetaDataError: {i18n.MsgErrCreateMetadata, "创建元数据失败"},
UpdateMetaDataError: {i18n.MsgErrUpdateMetadata, "更新元数据失败"},
GetMetaDataError: {i18n.MsgErrGetMetadata, "获取元数据失败"},
DeleteMetaDataError: {i18n.MsgErrDeleteMetadata, "删除元数据失败"},
ParameterInvalidError: {i18n.MsgErrParameterInvalid, "参数校验失败"},
ParameterTypeError: {i18n.MsgErrParameterType, "参数类型校验失败"},
ParameterValueError: {i18n.MsgErrParameterValue, "参数值校验失败"},
OperationForbidden: {i18n.MsgErrOperationForbid, "禁止执行该操作"},
NotPermissionError: {i18n.MsgErrNotPermission, "权限不足"},

// 存储集群操作异常
DescribeClusterError: "查询集群失败",
CreateClusterError: "创建集群失败",
GetClusterError: "获取集群失败",
DeleteClusterError: "删除集群失败",
GetClusterStatusError: "查询集群状态失败",
GetClusterEventError: "查询集群事件失败",
VerticalScalingError: "集群垂直扩缩容失败",
HorizontalScalingError: "集群水平扩缩容失败",
StartClusterError: "集群启动失败",
StopClusterError: "集群停止失败",
RestartClusterError: "集群重启失败",
UpgradeClusterError: "集群升级失败",
VolumeExpansionError: "集群磁盘扩缩容失败",
ExposeClusterError: "集群暴露服务失败",
DescribeOpsRequestError: "查询操作请求失败",
GetOpsRequestStatusError: "查询操作请求状态失败",
UpdateClusterError: "更新集群失败",
PartialUpdateClusterError: "局部更新集群失败",
GetClusterSvcError: "获取集群连接失败",
DescribeClusterError: {i18n.MsgErrClusterDescribe, "查询集群失败"},
CreateClusterError: {i18n.MsgErrClusterCreate, "创建集群失败"},
GetClusterError: {i18n.MsgErrClusterGet, "获取集群失败"},
DeleteClusterError: {i18n.MsgErrClusterDelete, "删除集群失败"},
GetClusterStatusError: {i18n.MsgErrClusterGetStatus, "查询集群状态失败"},
GetClusterEventError: {i18n.MsgErrClusterGetEvent, "查询集群事件失败"},
VerticalScalingError: {i18n.MsgErrClusterVerticalScale, "集群垂直扩缩容失败"},
HorizontalScalingError: {i18n.MsgErrClusterHorizScale, "集群水平扩缩容失败"},
StartClusterError: {i18n.MsgErrClusterStart, "集群启动失败"},
StopClusterError: {i18n.MsgErrClusterStop, "集群停止失败"},
RestartClusterError: {i18n.MsgErrClusterRestart, "集群重启失败"},
UpgradeClusterError: {i18n.MsgErrClusterUpgrade, "集群升级失败"},
VolumeExpansionError: {i18n.MsgErrClusterVolumeExpand, "集群磁盘扩缩容失败"},
ExposeClusterError: {i18n.MsgErrClusterExpose, "集群暴露服务失败"},
DescribeOpsRequestError: {i18n.MsgErrClusterDescribeOps, "查询操作请求失败"},
GetOpsRequestStatusError: {i18n.MsgErrClusterGetOpsStatus, "查询操作请求状态失败"},
UpdateClusterError: {i18n.MsgErrClusterUpdate, "更新集群失败"},
PartialUpdateClusterError: {i18n.MsgErrClusterPartialUpdate, "局部更新集群失败"},
GetClusterSvcError: {i18n.MsgErrClusterGetSvc, "获取集群连接失败"},

// k8s api server 调用异常
CreateK8sNsError: "创建命名空间失败",
DeleteK8sNsError: "删除命名空间失败",
GetPodLogError: "获取 Pod 日志失败",
K8sAPIServerTimeoutError: "K8s API Server 请求超时",
GetPodDetailError: "获取 Pod 详情失败",
CreateK8sClientError: "获取 K8s Client 失败",
DeleteK8sPodError: "删除实例节点失败",
InstallHelmChartErr: "安装 Helm chart 失败",
CreateK8sNsError: {i18n.MsgErrK8sCreateNs, "创建命名空间失败"},
DeleteK8sNsError: {i18n.MsgErrK8sDeleteNs, "删除命名空间失败"},
GetPodLogError: {i18n.MsgErrK8sGetPodLog, "获取 Pod 日志失败"},
K8sAPIServerTimeoutError: {i18n.MsgErrK8sAPITimeout, "K8s API Server 请求超时"},
GetPodDetailError: {i18n.MsgErrK8sGetPodDetail, "获取 Pod 详情失败"},
CreateK8sClientError: {i18n.MsgErrK8sCreateClient, "获取 K8s Client 失败"},
DeleteK8sPodError: {i18n.MsgErrK8sDeletePod, "删除实例节点失败"},
InstallHelmChartErr: {i18n.MsgErrK8sInstallHelm, "安装 Helm chart 失败"},

// 存储插件部署操作异常
InstallAddonError: "插件安装失败",
UninstallAddonError: "插件卸载失败",
UpgradeAddonError: "插件更新失败",
InstallAddonError: {i18n.MsgErrAddonInstall, "插件安装失败"},
UninstallAddonError: {i18n.MsgErrAddonUninstall, "插件卸载失败"},
UpgradeAddonError: {i18n.MsgErrAddonUpgrade, "插件更新失败"},

// 组件操作异常
DescribeComponentError: "查询组件失败",
GetComponentSvcError: "查询组件服务信息失败",
GetComponentPodsError: "查询组件实例列表失败",
DescribeComponentError: {i18n.MsgErrComponentDescribe, "查询组件失败"},
GetComponentSvcError: {i18n.MsgErrComponentGetSvc, "查询组件服务信息失败"},
GetComponentPodsError: {i18n.MsgErrComponentGetPods, "查询组件实例列表失败"},
}

// 权限异常
NotPermissionError: "权限不足",
// getCodeInfo 根据错误码获取错误信息
func getCodeInfo(code ErrorCode) errorCodeInfo {
if info, ok := codeInfoMap[code]; ok {
return info
}
return errorCodeInfo{MessageKey: "", DefaultMessage: "未知错误"}
}

// NewK8sDbsError 自定义错误
func NewK8sDbsError(code ErrorCode, err error) error {
errorDetail := codeTag[code]
info := getCodeInfo(code)
errorDetail := info.DefaultMessage
if err != nil {
errorDetail = err.Error()
}

return &K8sDbsError{
Code: code,
MessageKey: info.MessageKey,
Message: info.DefaultMessage,
ErrorDetail: errorDetail,
}
}

// NewK8sDbsErrorWithLocalizer 创建支持 i18n 的错误
func NewK8sDbsErrorWithLocalizer(code ErrorCode, err error, localizer *goi18n.Localizer) error {
info := getCodeInfo(code)
errorDetail := info.DefaultMessage
if err != nil {
errorDetail = err.Error()
}

message := info.DefaultMessage
if localizer != nil && info.MessageKey != "" {
message = i18n.Translate(localizer, info.MessageKey)
}

return &K8sDbsError{
Code: code,
Message: codeTag[code],
MessageKey: info.MessageKey,
Message: message,
ErrorDetail: errorDetail,
}
}

// GetMessageKey 获取错误码对应的 i18n 消息 Key
func GetMessageKey(code ErrorCode) string {
return getCodeInfo(code).MessageKey
}

// GetDefaultMessage 获取错误码对应的默认消息
func GetDefaultMessage(code ErrorCode) string {
return getCodeInfo(code).DefaultMessage
}
3 changes: 2 additions & 1 deletion dbm-services/k8s-dbs/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/imdario/mergo v0.3.16
github.com/jinzhu/copier v0.4.0
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4
github.com/nicksnyder/go-i18n/v2 v2.6.0
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.23.2
github.com/samber/lo v1.51.0
Expand All @@ -26,6 +27,7 @@ require (
golang.org/x/sync v0.17.0
golang.org/x/text v0.29.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.6.0
gorm.io/gorm v1.30.0
helm.sh/helm/v3 v3.18.5
Expand Down Expand Up @@ -198,7 +200,6 @@ require (
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.33.3 // indirect
k8s.io/apiserver v0.33.3 // indirect
k8s.io/component-base v0.33.3 // indirect
Expand Down
2 changes: 2 additions & 0 deletions dbm-services/k8s-dbs/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
Expand Down
41 changes: 41 additions & 0 deletions dbm-services/k8s-dbs/i18n/context/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
TencentBlueKing is pleased to support the open source community by making
蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.

Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.

Licensed under the MIT License (the "License");
you may not use this file except in compliance with the License.

You may obtain a copy of the License at
https://opensource.org/licenses/MIT

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package context 提供在 gin.Context 中存储和获取语言翻译器的功能。
package context

import (
"k8s-dbs/i18n"

"github.com/gin-gonic/gin"
goi18n "github.com/nicksnyder/go-i18n/v2/i18n"
)

// LanguageKey 在 gin.Context 中存储语言翻译器的 key
const LanguageKey = "i18n_language"

// GetLanguage 从 gin.Context 获取语言翻译器
func GetLanguage(c *gin.Context) *goi18n.Localizer {
if localizer, exists := c.Get(LanguageKey); exists {
if l, ok := localizer.(*goi18n.Localizer); ok {
return l
}
}
return i18n.NewLocalizer(i18n.DefaultLanguage)
}
Loading