Files
MyGoNavi/internal/app/methods_nacos.go
Syngnat cef273f228 feat(nacos): 新增 Nacos 数据源管理能力
- 支持 Nacos 连接测试及多命名空间资源树
- 提供配置搜索、发布、删除、历史回滚和变更监听
- 增加 Beta 发布、配置导入导出与批量操作
- 支持服务发现、实例管理、只读保护和多语言界面
- 补充 Wails 接口绑定及前后端回归测试

Refs #756
2026-07-28 15:58:22 +08:00

835 lines
32 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package app
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"strconv"
"strings"
"sync"
"time"
"GoNavi-Wails/internal/connection"
"GoNavi-Wails/internal/logger"
"GoNavi-Wails/internal/nacos"
)
var (
nacosCache = make(map[string]nacos.Client)
nacosCacheConfigs = make(map[string]connection.ConnectionConfig)
nacosCacheMu sync.Mutex
newNacosClientFunc = nacos.NewClient
)
// NacosConfigQuery is the frontend search payload.
type NacosConfigQuery struct {
NamespaceID string `json:"namespaceId"`
DataID string `json:"dataId,omitempty"`
Group string `json:"group,omitempty"`
AppName string `json:"appName,omitempty"`
PageNo int `json:"pageNo,omitempty"`
PageSize int `json:"pageSize,omitempty"`
Search string `json:"search,omitempty"`
}
// NacosPublishConfigPayload is the frontend publish payload.
type NacosPublishConfigPayload struct {
NamespaceID string `json:"namespaceId"`
DataID string `json:"dataId"`
Group string `json:"group"`
Content string `json:"content"`
Type string `json:"type,omitempty"`
AppName string `json:"appName,omitempty"`
Desc string `json:"desc,omitempty"`
BetaIPs string `json:"betaIps,omitempty"`
}
// NacosConfigIdentity identifies one config by dataId + group.
// Named type (not anonymous) so wailsjs models.ts generation stays valid.
type NacosConfigIdentity struct {
DataID string `json:"dataId"`
Group string `json:"group"`
}
// NacosExportConfigsOptions controls config export.
type NacosExportConfigsOptions struct {
NamespaceID string `json:"namespaceId"`
NamespaceName string `json:"namespaceName,omitempty"`
Scope string `json:"scope,omitempty"` // all | selected
Items []NacosConfigIdentity `json:"items,omitempty"`
}
// NacosImportConfigsOptions controls config import.
type NacosImportConfigsOptions struct {
NamespaceID string `json:"namespaceId"`
ConflictMode string `json:"conflictMode,omitempty"` // skip | overwrite
File string `json:"file,omitempty"`
Scope string `json:"scope,omitempty"` // all | selected
Items []NacosConfigIdentity `json:"items,omitempty"`
}
// NacosCreateNamespacePayload creates a namespace.
type NacosCreateNamespacePayload struct {
ID string `json:"id"`
ShowName string `json:"showName"`
Description string `json:"description,omitempty"`
}
// NacosUpdateNamespacePayload updates a namespace.
type NacosUpdateNamespacePayload struct {
ID string `json:"id"`
ShowName string `json:"showName"`
Description string `json:"description,omitempty"`
}
// NacosHistoryQuery lists config history.
type NacosHistoryQuery struct {
NamespaceID string `json:"namespaceId"`
DataID string `json:"dataId"`
Group string `json:"group"`
PageNo int `json:"pageNo,omitempty"`
PageSize int `json:"pageSize,omitempty"`
}
// NacosServiceQuery lists services.
type NacosServiceQuery struct {
NamespaceID string `json:"namespaceId"`
GroupName string `json:"groupName,omitempty"`
PageNo int `json:"pageNo,omitempty"`
PageSize int `json:"pageSize,omitempty"`
}
// NacosServicePayload creates/updates a service.
type NacosServicePayload struct {
NamespaceID string `json:"namespaceId"`
ServiceName string `json:"serviceName"`
GroupName string `json:"groupName,omitempty"`
ProtectThreshold float64 `json:"protectThreshold,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// NacosInstanceQuery lists instances.
type NacosInstanceQuery struct {
NamespaceID string `json:"namespaceId"`
ServiceName string `json:"serviceName"`
GroupName string `json:"groupName,omitempty"`
Clusters string `json:"clusters,omitempty"`
HealthyOnly bool `json:"healthyOnly,omitempty"`
}
// NacosInstancePayload mutates an instance.
type NacosInstancePayload struct {
NamespaceID string `json:"namespaceId"`
ServiceName string `json:"serviceName"`
GroupName string `json:"groupName,omitempty"`
IP string `json:"ip"`
Port int `json:"port"`
ClusterName string `json:"clusterName,omitempty"`
Weight float64 `json:"weight,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
Healthy *bool `json:"healthy,omitempty"`
Ephemeral *bool `json:"ephemeral,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
func formatNacosConnSummary(config connection.ConnectionConfig) string {
var b strings.Builder
b.WriteString("类型=nacos 地址=")
b.WriteString(strings.TrimSpace(config.Host))
b.WriteString(":")
b.WriteString(strconv.Itoa(config.Port))
if config.UseSSL {
b.WriteString(" SSL=on")
}
if user := strings.TrimSpace(config.User); user != "" {
b.WriteString(" 用户=")
b.WriteString(user)
}
if params := strings.TrimSpace(config.ConnectionParams); params != "" {
b.WriteString(" params=")
b.WriteString(params)
}
return b.String()
}
func getNacosClientCacheKey(config connection.ConnectionConfig) string {
normalized := normalizeCacheKeyConfig(config)
raw := strings.Join([]string{
"nacos",
strings.TrimSpace(normalized.Host),
strconv.Itoa(normalized.Port),
strings.TrimSpace(normalized.User),
strconv.FormatBool(normalized.UseSSL),
strings.TrimSpace(normalized.SSLMode),
strings.TrimSpace(normalized.ConnectionParams),
strings.TrimSpace(normalized.Database),
strconv.FormatBool(normalized.UseProxy),
strings.TrimSpace(normalized.Proxy.Type),
strings.TrimSpace(normalized.Proxy.Host),
strconv.Itoa(normalized.Proxy.Port),
strings.TrimSpace(normalized.Proxy.User),
}, "|")
sum := sha256.Sum256([]byte(raw))
return hex.EncodeToString(sum[:])
}
func (a *App) getNacosClient(config connection.ConnectionConfig) (nacos.Client, error) {
resolvedConfig, err := a.resolveConnectionSecrets(config)
if err != nil {
wrapped := wrapConnectError(config, err)
logger.Error(wrapped, "Nacos 密文解析失败:%s", formatNacosConnSummary(config))
return nil, wrapped
}
connectConfig, proxyErr := resolveDialConfigWithProxyFunc(resolvedConfig)
if proxyErr != nil {
wrapped := wrapConnectError(resolvedConfig, proxyErr)
logger.Error(wrapped, "Nacos 代理准备失败:%s", formatNacosConnSummary(resolvedConfig))
return nil, wrapped
}
connectConfig.Type = "nacos"
key := getNacosClientCacheKey(connectConfig)
shortKey := key
if len(shortKey) > 12 {
shortKey = shortKey[:12]
}
nacosCacheMu.Lock()
defer nacosCacheMu.Unlock()
if client, ok := nacosCache[key]; ok {
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
defer cancel()
if err := client.Ping(ctx); err == nil {
return client, nil
} else {
logger.Error(err, "缓存 Nacos 连接不可用准备重建缓存Key=%s", shortKey)
_ = client.Close()
delete(nacosCache, key)
delete(nacosCacheConfigs, key)
}
}
client := newNacosClientFunc()
if err := client.Connect(connectConfig); err != nil {
_ = client.Close()
wrapped := wrapConnectError(connectConfig, err)
logger.Error(wrapped, "Nacos 连接失败:%s 缓存Key=%s", formatNacosConnSummary(connectConfig), shortKey)
return nil, wrapped
}
nacosCache[key] = client
nacosCacheConfigs[key] = normalizeCacheKeyConfig(connectConfig)
logger.Infof("Nacos 连接成功并写入缓存:%s 缓存Key=%s", formatNacosConnSummary(connectConfig), shortKey)
return client, nil
}
func (a *App) openNacosClientIsolated(config connection.ConnectionConfig) (nacos.Client, error) {
resolvedConfig, err := a.resolveConnectionSecrets(config)
if err != nil {
wrapped := wrapConnectError(config, err)
logger.Error(wrapped, "Nacos 密文解析失败:%s", formatNacosConnSummary(config))
return nil, wrapped
}
connectConfig, proxyErr := resolveDialConfigWithProxyFunc(resolvedConfig)
if proxyErr != nil {
wrapped := wrapConnectError(resolvedConfig, proxyErr)
logger.Error(wrapped, "Nacos 代理准备失败:%s", formatNacosConnSummary(resolvedConfig))
return nil, wrapped
}
connectConfig.Type = "nacos"
client := newNacosClientFunc()
if err := client.Connect(connectConfig); err != nil {
_ = client.Close()
wrapped := wrapConnectError(connectConfig, err)
logger.Error(wrapped, "Nacos 临时连接失败:%s", formatNacosConnSummary(connectConfig))
return nil, wrapped
}
return client, nil
}
func (a *App) nacosOperationContext(config connection.ConnectionConfig) (context.Context, context.CancelFunc) {
timeout := config.Timeout
if timeout <= 0 {
timeout = 30
}
return context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
}
// NacosConnect establishes and caches a Nacos connection.
func (a *App) NacosConnect(config connection.ConnectionConfig) connection.QueryResult {
config.Type = "nacos"
_, err := a.getNacosClient(config)
if err != nil {
logger.Error(err, "NacosConnect 连接失败:%s", formatNacosConnSummary(config))
return connection.QueryResult{Success: false, Message: err.Error()}
}
logger.Infof("NacosConnect 连接成功:%s", formatNacosConnSummary(config))
return connection.QueryResult{Success: true, Message: a.appText("nacos.backend.message.connect_success", nil)}
}
// NacosTestConnection tests connectivity without reusing long-lived cache.
func (a *App) NacosTestConnection(config connection.ConnectionConfig) connection.QueryResult {
config.Type = "nacos"
client, err := a.openNacosClientIsolated(config)
if err != nil {
logger.Error(err, "NacosTestConnection 连接失败:%s", formatNacosConnSummary(config))
return connection.QueryResult{Success: false, Message: err.Error()}
}
if client != nil {
if closeErr := client.Close(); closeErr != nil {
logger.Error(closeErr, "NacosTestConnection 释放临时连接失败:%s", formatNacosConnSummary(config))
return connection.QueryResult{
Success: false,
Message: a.appText("nacos.backend.error.test_connection_close_failed", map[string]any{"detail": closeErr.Error()}),
}
}
}
logger.Infof("NacosTestConnection 连接成功:%s", formatNacosConnSummary(config))
return connection.QueryResult{Success: true, Message: a.appText("nacos.backend.message.connect_success", nil)}
}
// NacosListNamespaces lists namespaces for a connection.
func (a *App) NacosListNamespaces(config connection.ConnectionConfig) connection.QueryResult {
config.Type = "nacos"
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
namespaces, err := client.ListNamespaces(ctx)
if err != nil {
logger.Error(err, "NacosListNamespaces 失败:%s", formatNacosConnSummary(config))
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Data: namespaces}
}
// NacosListConfigGroups lists unique config groups under a namespace.
func (a *App) NacosListConfigGroups(config connection.ConnectionConfig, namespaceID string) connection.QueryResult {
config.Type = "nacos"
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
groups, err := client.ListConfigGroups(ctx, namespaceID)
if err != nil {
logger.Error(err, "NacosListConfigGroups 失败:%s", formatNacosConnSummary(config))
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Data: groups}
}
// NacosSearchConfigs searches configs under a namespace.
func (a *App) NacosSearchConfigs(config connection.ConnectionConfig, query NacosConfigQuery) connection.QueryResult {
config.Type = "nacos"
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
page, err := client.SearchConfigs(ctx, nacos.ConfigQuery{
NamespaceID: query.NamespaceID,
DataID: query.DataID,
Group: query.Group,
AppName: query.AppName,
PageNo: query.PageNo,
PageSize: query.PageSize,
Search: query.Search,
})
if err != nil {
logger.Error(err, "NacosSearchConfigs 失败:%s", formatNacosConnSummary(config))
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Data: page}
}
// NacosGetConfig loads one config.
func (a *App) NacosGetConfig(config connection.ConnectionConfig, namespaceID, group, dataID string) connection.QueryResult {
config.Type = "nacos"
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
detail, err := client.GetConfig(ctx, namespaceID, group, dataID)
if err != nil {
logger.Error(err, "NacosGetConfig 失败dataId=%s group=%s", dataID, group)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Data: detail}
}
// NacosPublishConfig creates or updates a config.
func (a *App) NacosPublishConfig(config connection.ConnectionConfig, payload NacosPublishConfigPayload) connection.QueryResult {
config.Type = "nacos"
if err := a.ensureNacosDataEditAllowed(config); err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
if err := client.PublishConfig(ctx, nacos.PublishRequest{
NamespaceID: payload.NamespaceID,
DataID: payload.DataID,
Group: payload.Group,
Content: payload.Content,
Type: payload.Type,
AppName: payload.AppName,
Desc: payload.Desc,
BetaIPs: payload.BetaIPs,
}); err != nil {
logger.Error(err, "NacosPublishConfig 失败dataId=%s group=%s", payload.DataID, payload.Group)
return connection.QueryResult{Success: false, Message: err.Error()}
}
if strings.TrimSpace(payload.BetaIPs) != "" {
return connection.QueryResult{
Success: true,
Message: a.appText("nacos.backend.message.beta_publish_success", nil),
}
}
return connection.QueryResult{
Success: true,
Message: a.appText("nacos.backend.message.publish_success", nil),
}
}
// NacosGetBetaConfig loads beta config for one dataId/group.
func (a *App) NacosGetBetaConfig(config connection.ConnectionConfig, namespaceID, group, dataID string) connection.QueryResult {
config.Type = "nacos"
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
detail, err := client.GetBetaConfig(ctx, namespaceID, group, dataID)
if err != nil {
logger.Error(err, "NacosGetBetaConfig 失败dataId=%s group=%s", dataID, group)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Data: detail}
}
// NacosStopBetaConfig stops beta publish.
func (a *App) NacosStopBetaConfig(config connection.ConnectionConfig, namespaceID, group, dataID string) connection.QueryResult {
config.Type = "nacos"
if err := a.ensureNacosDataEditAllowed(config); err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
if err := client.StopBetaConfig(ctx, namespaceID, group, dataID); err != nil {
logger.Error(err, "NacosStopBetaConfig 失败dataId=%s group=%s", dataID, group)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{
Success: true,
Message: a.appText("nacos.backend.message.beta_stop_success", nil),
}
}
// NacosDeleteConfig deletes a config.
func (a *App) NacosDeleteConfig(config connection.ConnectionConfig, namespaceID, group, dataID string) connection.QueryResult {
config.Type = "nacos"
if err := a.ensureNacosDataEditAllowed(config); err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
if err := client.DeleteConfig(ctx, namespaceID, group, dataID); err != nil {
logger.Error(err, "NacosDeleteConfig 失败dataId=%s group=%s", dataID, group)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{
Success: true,
Message: a.appText("nacos.backend.message.delete_success", nil),
}
}
// NacosCreateNamespace creates a namespace.
func (a *App) NacosCreateNamespace(config connection.ConnectionConfig, payload NacosCreateNamespacePayload) connection.QueryResult {
config.Type = "nacos"
if err := a.ensureNacosStructureEditAllowed(config); err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
if err := client.CreateNamespace(ctx, nacos.CreateNamespaceRequest{
ID: payload.ID,
ShowName: payload.ShowName,
Description: payload.Description,
}); err != nil {
logger.Error(err, "NacosCreateNamespace 失败id=%s name=%s", payload.ID, payload.ShowName)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{
Success: true,
Message: a.appText("nacos.backend.message.namespace_create_success", nil),
}
}
// NacosUpdateNamespace updates a namespace.
func (a *App) NacosUpdateNamespace(config connection.ConnectionConfig, payload NacosUpdateNamespacePayload) connection.QueryResult {
config.Type = "nacos"
if err := a.ensureNacosStructureEditAllowed(config); err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
if err := client.UpdateNamespace(ctx, nacos.UpdateNamespaceRequest{
ID: payload.ID,
ShowName: payload.ShowName,
Description: payload.Description,
}); err != nil {
logger.Error(err, "NacosUpdateNamespace 失败id=%s", payload.ID)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{
Success: true,
Message: a.appText("nacos.backend.message.namespace_update_success", nil),
}
}
// NacosDeleteNamespace deletes a namespace.
func (a *App) NacosDeleteNamespace(config connection.ConnectionConfig, namespaceID string) connection.QueryResult {
config.Type = "nacos"
if err := a.ensureNacosStructureEditAllowed(config); err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
if err := client.DeleteNamespace(ctx, namespaceID); err != nil {
logger.Error(err, "NacosDeleteNamespace 失败id=%s", namespaceID)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{
Success: true,
Message: a.appText("nacos.backend.message.namespace_delete_success", nil),
}
}
// NacosListConfigHistory lists history for one config.
func (a *App) NacosListConfigHistory(config connection.ConnectionConfig, query NacosHistoryQuery) connection.QueryResult {
config.Type = "nacos"
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
page, err := client.ListConfigHistory(ctx, nacos.HistoryQuery{
NamespaceID: query.NamespaceID,
DataID: query.DataID,
Group: query.Group,
PageNo: query.PageNo,
PageSize: query.PageSize,
})
if err != nil {
logger.Error(err, "NacosListConfigHistory 失败dataId=%s group=%s", query.DataID, query.Group)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Data: page}
}
// NacosGetConfigHistory loads one history detail.
func (a *App) NacosGetConfigHistory(config connection.ConnectionConfig, namespaceID, group, dataID, nid string) connection.QueryResult {
config.Type = "nacos"
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
item, err := client.GetConfigHistory(ctx, namespaceID, group, dataID, nid)
if err != nil {
logger.Error(err, "NacosGetConfigHistory 失败nid=%s dataId=%s", nid, dataID)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Data: item}
}
// NacosListServices lists services under a namespace.
func (a *App) NacosListServices(config connection.ConnectionConfig, query NacosServiceQuery) connection.QueryResult {
config.Type = "nacos"
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
page, err := client.ListServices(ctx, nacos.ServiceQuery{
NamespaceID: query.NamespaceID,
GroupName: query.GroupName,
PageNo: query.PageNo,
PageSize: query.PageSize,
})
if err != nil {
logger.Error(err, "NacosListServices 失败:%s", formatNacosConnSummary(config))
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Data: page}
}
// NacosGetService loads service detail.
func (a *App) NacosGetService(config connection.ConnectionConfig, namespaceID, serviceName, groupName string) connection.QueryResult {
config.Type = "nacos"
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
detail, err := client.GetService(ctx, namespaceID, serviceName, groupName)
if err != nil {
logger.Error(err, "NacosGetService 失败service=%s", serviceName)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Data: detail}
}
// NacosCreateService creates a service.
func (a *App) NacosCreateService(config connection.ConnectionConfig, payload NacosServicePayload) connection.QueryResult {
config.Type = "nacos"
if err := a.ensureNacosStructureEditAllowed(config); err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
if err := client.CreateService(ctx, nacos.CreateServiceRequest{
NamespaceID: payload.NamespaceID,
ServiceName: payload.ServiceName,
GroupName: payload.GroupName,
ProtectThreshold: payload.ProtectThreshold,
Metadata: payload.Metadata,
}); err != nil {
logger.Error(err, "NacosCreateService 失败service=%s", payload.ServiceName)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Message: a.appText("nacos.backend.message.service_create_success", nil)}
}
// NacosUpdateService updates a service.
func (a *App) NacosUpdateService(config connection.ConnectionConfig, payload NacosServicePayload) connection.QueryResult {
config.Type = "nacos"
if err := a.ensureNacosStructureEditAllowed(config); err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
if err := client.UpdateService(ctx, nacos.UpdateServiceRequest{
NamespaceID: payload.NamespaceID,
ServiceName: payload.ServiceName,
GroupName: payload.GroupName,
ProtectThreshold: payload.ProtectThreshold,
Metadata: payload.Metadata,
}); err != nil {
logger.Error(err, "NacosUpdateService 失败service=%s", payload.ServiceName)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Message: a.appText("nacos.backend.message.service_update_success", nil)}
}
// NacosDeleteService deletes a service.
func (a *App) NacosDeleteService(config connection.ConnectionConfig, namespaceID, serviceName, groupName string) connection.QueryResult {
config.Type = "nacos"
if err := a.ensureNacosStructureEditAllowed(config); err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
if err := client.DeleteService(ctx, namespaceID, serviceName, groupName); err != nil {
logger.Error(err, "NacosDeleteService 失败service=%s", serviceName)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Message: a.appText("nacos.backend.message.service_delete_success", nil)}
}
// NacosListInstances lists instances of a service.
func (a *App) NacosListInstances(config connection.ConnectionConfig, query NacosInstanceQuery) connection.QueryResult {
config.Type = "nacos"
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
list, err := client.ListInstances(ctx, nacos.InstanceQuery{
NamespaceID: query.NamespaceID,
ServiceName: query.ServiceName,
GroupName: query.GroupName,
Clusters: query.Clusters,
HealthyOnly: query.HealthyOnly,
})
if err != nil {
logger.Error(err, "NacosListInstances 失败service=%s", query.ServiceName)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Data: list}
}
// NacosGetInstance loads one instance.
func (a *App) NacosGetInstance(config connection.ConnectionConfig, payload NacosInstancePayload) connection.QueryResult {
config.Type = "nacos"
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
inst, err := client.GetInstance(ctx, toNacosInstanceRequest(payload))
if err != nil {
logger.Error(err, "NacosGetInstance 失败:%s:%d", payload.IP, payload.Port)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Data: inst}
}
// NacosRegisterInstance registers an instance.
func (a *App) NacosRegisterInstance(config connection.ConnectionConfig, payload NacosInstancePayload) connection.QueryResult {
config.Type = "nacos"
if err := a.ensureNacosDataEditAllowed(config); err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
if err := client.RegisterInstance(ctx, toNacosInstanceRequest(payload)); err != nil {
logger.Error(err, "NacosRegisterInstance 失败:%s:%d", payload.IP, payload.Port)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Message: a.appText("nacos.backend.message.instance_register_success", nil)}
}
// NacosUpdateInstance updates an instance.
func (a *App) NacosUpdateInstance(config connection.ConnectionConfig, payload NacosInstancePayload) connection.QueryResult {
config.Type = "nacos"
if err := a.ensureNacosDataEditAllowed(config); err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
if err := client.UpdateInstance(ctx, toNacosInstanceRequest(payload)); err != nil {
logger.Error(err, "NacosUpdateInstance 失败:%s:%d", payload.IP, payload.Port)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Message: a.appText("nacos.backend.message.instance_update_success", nil)}
}
// NacosDeregisterInstance deregisters an instance.
func (a *App) NacosDeregisterInstance(config connection.ConnectionConfig, payload NacosInstancePayload) connection.QueryResult {
config.Type = "nacos"
if err := a.ensureNacosDataEditAllowed(config); err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
if err := client.DeregisterInstance(ctx, toNacosInstanceRequest(payload)); err != nil {
logger.Error(err, "NacosDeregisterInstance 失败:%s:%d", payload.IP, payload.Port)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Message: a.appText("nacos.backend.message.instance_deregister_success", nil)}
}
// NacosUpdateInstanceHealth updates instance health.
func (a *App) NacosUpdateInstanceHealth(config connection.ConnectionConfig, payload NacosInstancePayload) connection.QueryResult {
config.Type = "nacos"
if err := a.ensureNacosDataEditAllowed(config); err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
client, err := a.getNacosClient(config)
if err != nil {
return connection.QueryResult{Success: false, Message: err.Error()}
}
ctx, cancel := a.nacosOperationContext(config)
defer cancel()
if err := client.UpdateInstanceHealth(ctx, toNacosInstanceRequest(payload)); err != nil {
logger.Error(err, "NacosUpdateInstanceHealth 失败:%s:%d", payload.IP, payload.Port)
return connection.QueryResult{Success: false, Message: err.Error()}
}
return connection.QueryResult{Success: true, Message: a.appText("nacos.backend.message.instance_health_success", nil)}
}
func toNacosInstanceRequest(payload NacosInstancePayload) nacos.InstanceRequest {
return nacos.InstanceRequest{
NamespaceID: payload.NamespaceID,
ServiceName: payload.ServiceName,
GroupName: payload.GroupName,
IP: payload.IP,
Port: payload.Port,
ClusterName: payload.ClusterName,
Weight: payload.Weight,
Enabled: payload.Enabled,
Healthy: payload.Healthy,
Ephemeral: payload.Ephemeral,
Metadata: payload.Metadata,
}
}
func (a *App) ensureNacosDataEditAllowed(config connection.ConnectionConfig) error {
// Nacos is outside the SQL read-only type set; honor explicit flags directly.
if config.ReadOnly || config.Protection.RestrictDataEdit {
return fmt.Errorf("%s", a.appText("nacos.backend.error.read_only", nil))
}
return nil
}
func (a *App) ensureNacosStructureEditAllowed(config connection.ConnectionConfig) error {
if config.ReadOnly || config.Protection.RestrictStructureEdit || config.Protection.RestrictDataEdit {
return fmt.Errorf("%s", a.appText("nacos.backend.error.read_only", nil))
}
return nil
}