新增:数据库可见性过滤与连接管理增强
功能: - 支持配置 MySQL/MongoDB 可见数据库列表 - 连接删除时自动清理关联数据并关闭连接池 - 新增加载数据库列表 API - 数据库错误提示优化 改进: - 代码简化:消除重复的表单验证和密码处理逻辑 - ResultPanel 表格高度计算重构 - 删除调试日志和临时文件 后端: - 新增 VisibleDatabases 字段到连接模型 - DeleteConnection 使用事务确保数据一致性 - LoadAllDatabases 支持 MySQL/MongoDB 数据库列表加载
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"u-desk/internal/service"
|
||||
"u-desk/internal/storage"
|
||||
"u-desk/internal/storage/models"
|
||||
)
|
||||
|
||||
// ConnectionAPI 连接管理API
|
||||
type ConnectionAPI struct {
|
||||
connService *service.ConnectionService
|
||||
connService *storage.ConnectionService
|
||||
}
|
||||
|
||||
// NewConnectionAPI 创建连接管理API
|
||||
func NewConnectionAPI() (*ConnectionAPI, error) {
|
||||
connService, err := service.NewConnectionService()
|
||||
connService, err := storage.NewConnectionService()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -21,29 +21,31 @@ func NewConnectionAPI() (*ConnectionAPI, error) {
|
||||
|
||||
// SaveConnectionRequest 保存连接请求结构体
|
||||
type SaveConnectionRequest struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Database string `json:"database"`
|
||||
Options string `json:"options"`
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Database string `json:"database"`
|
||||
Options string `json:"options"`
|
||||
VisibleDatabases string `json:"visible_databases"`
|
||||
}
|
||||
|
||||
// SaveDbConnection 保存数据库连接配置
|
||||
func (api *ConnectionAPI) SaveDbConnection(req SaveConnectionRequest) error {
|
||||
conn := &models.DbConnection{
|
||||
ID: req.ID,
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
Host: req.Host,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Database: req.Database,
|
||||
Options: req.Options,
|
||||
ID: req.ID,
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
Host: req.Host,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Database: req.Database,
|
||||
Options: req.Options,
|
||||
VisibleDatabases: req.VisibleDatabases,
|
||||
}
|
||||
return api.connService.SaveConnection(conn)
|
||||
}
|
||||
@@ -59,16 +61,17 @@ func (api *ConnectionAPI) ListDbConnections() ([]map[string]interface{}, error)
|
||||
timeFormat := "2006-01-02 15:04:05"
|
||||
for i, conn := range connections {
|
||||
result[i] = map[string]interface{}{
|
||||
"id": conn.ID,
|
||||
"name": conn.Name,
|
||||
"type": conn.Type,
|
||||
"host": conn.Host,
|
||||
"port": conn.Port,
|
||||
"username": conn.Username,
|
||||
"database": conn.Database,
|
||||
"options": conn.Options,
|
||||
"created_at": conn.CreatedAt.Format(timeFormat),
|
||||
"updated_at": conn.UpdatedAt.Format(timeFormat),
|
||||
"id": conn.ID,
|
||||
"name": conn.Name,
|
||||
"type": conn.Type,
|
||||
"host": conn.Host,
|
||||
"port": conn.Port,
|
||||
"username": conn.Username,
|
||||
"database": conn.Database,
|
||||
"options": conn.Options,
|
||||
"visible_databases": conn.VisibleDatabases,
|
||||
"created_at": conn.CreatedAt.Format(timeFormat),
|
||||
"updated_at": conn.UpdatedAt.Format(timeFormat),
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
@@ -79,7 +82,11 @@ func (api *ConnectionAPI) DeleteDbConnection(id uint) error {
|
||||
}
|
||||
|
||||
func (api *ConnectionAPI) TestDbConnection(id uint) error {
|
||||
return api.connService.TestConnection(id)
|
||||
conn, err := api.connService.GetConnection(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return api.connService.TestConnection(conn)
|
||||
}
|
||||
|
||||
// TestConnectionRequest 测试连接请求结构体(不保存数据)
|
||||
@@ -107,3 +114,29 @@ func (api *ConnectionAPI) TestDbConnectionWithParams(req TestConnectionRequest)
|
||||
req.ID,
|
||||
)
|
||||
}
|
||||
|
||||
// LoadAllDatabasesRequest 加载全部数据库请求结构体
|
||||
type LoadAllDatabasesRequest struct {
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Database string `json:"database"`
|
||||
Options string `json:"options"`
|
||||
}
|
||||
|
||||
// LoadAllDatabases 加载全部数据库列表
|
||||
func (api *ConnectionAPI) LoadAllDatabases(req LoadAllDatabasesRequest) ([]string, error) {
|
||||
return api.connService.LoadAllDatabases(
|
||||
req.Type,
|
||||
req.Host,
|
||||
req.Port,
|
||||
req.Username,
|
||||
req.Password,
|
||||
req.Database,
|
||||
req.Options,
|
||||
req.ID,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"u-desk/internal/crypto"
|
||||
@@ -55,13 +56,14 @@ func (s *ConnectionService) SaveConnection(conn *models.DbConnection) error {
|
||||
if conn.ID > 0 {
|
||||
// 更新模式
|
||||
updateData := map[string]interface{}{
|
||||
"name": conn.Name,
|
||||
"type": conn.Type,
|
||||
"host": conn.Host,
|
||||
"port": conn.Port,
|
||||
"username": conn.Username,
|
||||
"database": conn.Database,
|
||||
"options": conn.Options,
|
||||
"name": conn.Name,
|
||||
"type": conn.Type,
|
||||
"host": conn.Host,
|
||||
"port": conn.Port,
|
||||
"username": conn.Username,
|
||||
"database": conn.Database,
|
||||
"options": conn.Options,
|
||||
"visible_databases": conn.VisibleDatabases,
|
||||
}
|
||||
|
||||
// 如果提供了新密码,加密后更新
|
||||
@@ -111,7 +113,26 @@ func (s *ConnectionService) GetConnection(id uint) (*models.DbConnection, error)
|
||||
|
||||
// DeleteConnection 删除连接配置
|
||||
func (s *ConnectionService) DeleteConnection(id uint) error {
|
||||
return s.db.Delete(&models.DbConnection{}, id).Error
|
||||
var conn models.DbConnection
|
||||
if err := s.db.First(&conn, id).Error; err != nil {
|
||||
return nil // 连接不存在视为成功
|
||||
}
|
||||
|
||||
// 使用事务删除
|
||||
return s.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 清理关联数据
|
||||
tx.Where("connection_id = ?", id).Delete(&models.SqlResultHistory{})
|
||||
tx.Where("connection_id = ?", id).Delete(&models.SqlTab{})
|
||||
|
||||
// 删除连接
|
||||
if err := tx.Delete(&conn).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 关闭连接池
|
||||
dbclient.GetPool().CloseConnection(id, conn.Type)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// TestConnection 测试连接(需要根据类型调用不同的测试方法)
|
||||
@@ -163,3 +184,127 @@ func testRedisConnection(host string, port int, password string) error {
|
||||
func testMongoConnection(host string, port int, username, password, database, authSource, authMechanism string) error {
|
||||
return dbclient.TestMongoConnectionWithOptions(host, port, username, password, database, authSource, authMechanism)
|
||||
}
|
||||
|
||||
// TestConnectionWithParams 使用参数测试连接(不保存数据)
|
||||
func (s *ConnectionService) TestConnectionWithParams(dbType, host string, port int, username, password, database, options string, id uint) error {
|
||||
// 如果是编辑模式且有ID,获取已保存的密码
|
||||
if id > 0 && password == "" {
|
||||
conn, err := s.GetConnection(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取连接信息失败: %v", err)
|
||||
}
|
||||
decryptPassword, err := crypto.DecryptPassword(conn.Password)
|
||||
if err != nil {
|
||||
return fmt.Errorf("密码解密失败: %v", err)
|
||||
}
|
||||
password = decryptPassword
|
||||
}
|
||||
|
||||
// 根据类型测试连接
|
||||
switch dbType {
|
||||
case "mysql":
|
||||
return testMySQLConnection(host, port, username, password, database)
|
||||
case "redis":
|
||||
return testRedisConnection(host, port, password)
|
||||
case "mongo":
|
||||
// 解析 Options 获取 MongoDB 连接参数
|
||||
authSource := ""
|
||||
authMechanism := ""
|
||||
if options != "" {
|
||||
var opts map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(options), &opts); err == nil {
|
||||
if as, ok := opts["authSource"].(string); ok && as != "" {
|
||||
authSource = as
|
||||
}
|
||||
if am, ok := opts["authMechanism"].(string); ok && am != "" {
|
||||
authMechanism = am
|
||||
}
|
||||
}
|
||||
}
|
||||
return testMongoConnection(host, port, username, password, database, authSource, authMechanism)
|
||||
default:
|
||||
return fmt.Errorf("不支持的数据库类型: %s", dbType)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadAllDatabases 加载全部数据库列表
|
||||
func (s *ConnectionService) LoadAllDatabases(dbType, host string, port int, username, password, database, options string, id uint) ([]string, error) {
|
||||
// 如果是编辑模式且有ID,获取已保存的密码
|
||||
if id > 0 && password == "" {
|
||||
conn, err := s.GetConnection(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取连接信息失败: %v", err)
|
||||
}
|
||||
decryptPassword, err := crypto.DecryptPassword(conn.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("密码解密失败: %v", err)
|
||||
}
|
||||
password = decryptPassword
|
||||
}
|
||||
|
||||
// 根据类型加载数据库列表
|
||||
switch dbType {
|
||||
case "mysql":
|
||||
return loadMySQLDatabases(host, port, username, password, database)
|
||||
case "mongo":
|
||||
return loadMongoDatabases(host, port, username, password, database, options)
|
||||
case "redis":
|
||||
// Redis 没有数据库概念,返回空列表
|
||||
return []string{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("不支持的数据库类型: %s", dbType)
|
||||
}
|
||||
}
|
||||
|
||||
// loadMySQLDatabases 加载 MySQL 数据库列表
|
||||
func loadMySQLDatabases(host string, port int, username, password, defaultDatabase string) ([]string, error) {
|
||||
config := &dbclient.MySQLConfig{
|
||||
Host: host,
|
||||
Port: port,
|
||||
Username: username,
|
||||
Password: password,
|
||||
Database: defaultDatabase,
|
||||
}
|
||||
client, err := dbclient.NewMySQLClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
return client.ListDatabases(context.Background())
|
||||
}
|
||||
|
||||
// loadMongoDatabases 加载 MongoDB 数据库列表
|
||||
func loadMongoDatabases(host string, port int, username, password, defaultDatabase, options string) ([]string, error) {
|
||||
// 解析 Options 获取 MongoDB 连接参数
|
||||
authSource := ""
|
||||
authMechanism := ""
|
||||
if options != "" {
|
||||
var opts map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(options), &opts); err == nil {
|
||||
if as, ok := opts["authSource"].(string); ok && as != "" {
|
||||
authSource = as
|
||||
}
|
||||
if am, ok := opts["authMechanism"].(string); ok && am != "" {
|
||||
authMechanism = am
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mongoConfig := &dbclient.MongoConfig{
|
||||
Host: host,
|
||||
Port: port,
|
||||
Username: username,
|
||||
Password: password,
|
||||
Database: defaultDatabase,
|
||||
AuthSource: authSource,
|
||||
AuthMechanism: authMechanism,
|
||||
}
|
||||
client, err := dbclient.NewMongoClient(mongoConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
return client.ListDatabases(context.Background())
|
||||
}
|
||||
|
||||
@@ -6,17 +6,18 @@ import (
|
||||
|
||||
// DbConnection 数据库连接配置
|
||||
type DbConnection struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"type:varchar(100);not null" json:"name"` // 连接名称
|
||||
Type string `gorm:"type:varchar(20);not null" json:"type"` // 数据库类型: mysql/redis/mongo
|
||||
Host string `gorm:"type:varchar(255);not null" json:"host"` // 主机地址
|
||||
Port int `gorm:"not null" json:"port"` // 端口
|
||||
Username string `gorm:"type:varchar(100)" json:"username"` // 用户名
|
||||
Password string `gorm:"type:varchar(500)" json:"-"` // 密码(加密存储,不返回)
|
||||
Database string `gorm:"type:varchar(100)" json:"database"` // 数据库名(MySQL/MongoDB)
|
||||
Options string `gorm:"type:text" json:"options"` // 额外选项(JSON格式)
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"type:varchar(100);not null" json:"name"` // 连接名称
|
||||
Type string `gorm:"type:varchar(20);not null" json:"type"` // 数据库类型: mysql/redis/mongo
|
||||
Host string `gorm:"type:varchar(255);not null" json:"host"` // 主机地址
|
||||
Port int `gorm:"not null" json:"port"` // 端口
|
||||
Username string `gorm:"type:varchar(100)" json:"username"` // 用户名
|
||||
Password string `gorm:"type:varchar(500)" json:"-"` // 密码(加密存储,不返回)
|
||||
Database string `gorm:"type:varchar(100)" json:"database"` // 数据库名(MySQL/MongoDB)
|
||||
Options string `gorm:"type:text" json:"options"` // 额外选项(JSON格式)
|
||||
VisibleDatabases string `gorm:"type:text" json:"visible_databases"` // 可见数据库列表(JSON数组,为空则全部可见)
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
|
||||
Reference in New Issue
Block a user