gtong 1 månad sedan
förälder
incheckning
77ba2d1c54
38 ändrade filer med 2468 tillägg och 1354 borttagningar
  1. 56 0
      DBconfig/sqlfiles/20251121_create_connections_split.sql
  2. 37 0
      DBconfig/sqlfiles/20251121_create_scripts_table.sql
  3. BIN
      DBconfig/storage.db
  4. 125 0
      app.log
  5. 91 5
      service/internal/common/databases/drivers/mysql/v8/meta.go
  6. 34 0
      service/internal/common/databases/drivers/resources.go
  7. 8 0
      service/internal/common/databases/drivers/resources/README.md
  8. 8 0
      service/internal/common/databases/interfaces.go
  9. 21 1
      service/internal/common/databases/meta/meta.go
  10. 17 17
      service/internal/common/manager/connection/connection_pool.go
  11. 115 111
      service/internal/common/manager/storage/README.md
  12. 160 113
      service/internal/common/manager/storage/db_storage/connection_groups.go
  13. 156 140
      service/internal/common/manager/storage/db_storage/connection_operations.go
  14. 369 172
      service/internal/common/manager/storage/db_storage/manager.go
  15. 8 56
      service/internal/common/manager/storage/db_storage/script_groups.go
  16. 405 0
      service/internal/common/manager/storage/db_storage/scripts.go
  17. 2 300
      service/internal/common/manager/storage/db_storage/sql_scripts.go
  18. 32 0
      service/internal/common/manager/storage/db_storage/utils.go
  19. 15 14
      service/internal/common/manager/storage/interface.go
  20. 98 54
      service/internal/common/manager/storage/types/types.go
  21. 12 12
      service/internal/common/response/response.go
  22. 3 2
      service/internal/config/README.md
  23. 1 1
      service/internal/config/config.go
  24. 22 22
      service/internal/config/data.toml
  25. 10 8
      service/internal/modules/connection_pool/API_EXAMPLES.md
  26. 6 6
      service/internal/modules/connection_pool/api/types.go
  27. 7 7
      service/internal/modules/connection_pool/service/connection_pool_service.go
  28. 2 2
      service/internal/modules/data_query/service/service.go
  29. 41 0
      service/internal/modules/database_meta/API.md
  30. 6 0
      service/internal/modules/database_meta/api/api.go
  31. 16 0
      service/internal/modules/database_meta/handler/handler.go
  32. 1 0
      service/internal/modules/database_meta/route.go
  33. 26 0
      service/internal/modules/database_meta/service/service.go
  34. 108 84
      service/internal/modules/db_conn_storage/API_EXAMPLES.md
  35. 160 94
      service/internal/modules/db_conn_storage/api/types.go
  36. 55 31
      service/internal/modules/db_conn_storage/handler/storage_handler.go
  37. 6 6
      service/internal/modules/db_conn_storage/routes.go
  38. 229 96
      service/internal/modules/db_conn_storage/service/storage_service.go

+ 56 - 0
DBconfig/sqlfiles/20251121_create_connections_split.sql

@@ -0,0 +1,56 @@
+-- 创建基础连接表
+CREATE TABLE IF NOT EXISTS connections (
+  id TEXT PRIMARY KEY,
+  group_id TEXT NOT NULL,
+  name TEXT NOT NULL,
+  description TEXT,
+  kind TEXT NOT NULL,
+  color TEXT,
+  auto_connect INTEGER DEFAULT 0,
+  display_order INTEGER DEFAULT 0,
+  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+CREATE INDEX IF NOT EXISTS idx_connections_group_id ON connections(group_id);
+
+-- 数据库连接特有信息
+CREATE TABLE IF NOT EXISTS db_connections (
+  connection_id TEXT PRIMARY KEY REFERENCES connections(id) ON DELETE CASCADE,
+  type TEXT NOT NULL,
+  version TEXT,
+  server TEXT,
+  port INTEGER,
+  username TEXT,
+  password TEXT,
+  database_name TEXT,
+  connection_string TEXT,
+  use_ssh_tunnel INTEGER DEFAULT 0,
+  ssh_tunnel_connection_id TEXT,
+  last_connected DATETIME
+);
+CREATE INDEX IF NOT EXISTS idx_db_connections_server ON db_connections(server);
+
+-- 服务器/主机类连接特有信息
+CREATE TABLE IF NOT EXISTS server_connections (
+  connection_id TEXT PRIMARY KEY REFERENCES connections(id) ON DELETE CASCADE,
+  type TEXT NOT NULL,
+  version TEXT,
+  server TEXT,
+  port INTEGER,
+  username TEXT,
+  auth_type TEXT,
+  private_key TEXT,
+  use_sudo INTEGER DEFAULT 0
+);
+CREATE INDEX IF NOT EXISTS idx_server_connections_server ON server_connections(server);
+
+-- 连接间关联/层级关系表
+CREATE TABLE IF NOT EXISTS connection_links (
+  id TEXT PRIMARY KEY,
+  parent_connection_id TEXT NOT NULL REFERENCES connections(id) ON DELETE CASCADE,
+  child_connection_id TEXT NOT NULL REFERENCES connections(id) ON DELETE CASCADE,
+  relation_type TEXT,
+  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+CREATE INDEX IF NOT EXISTS idx_connection_links_parent ON connection_links(parent_connection_id);
+CREATE INDEX IF NOT EXISTS idx_connection_links_child ON connection_links(child_connection_id);

+ 37 - 0
DBconfig/sqlfiles/20251121_create_scripts_table.sql

@@ -0,0 +1,37 @@
+-- Migration: create `scripts` table (destructive, does NOT preserve old `sql_scripts` data)
+-- Generated: 2025-11-21
+
+PRAGMA foreign_keys = OFF;
+BEGIN TRANSACTION;
+
+-- Drop legacy table if exists (destructive)
+DROP TABLE IF EXISTS sql_scripts;
+
+-- Create new `scripts` table with extended columns
+CREATE TABLE IF NOT EXISTS scripts (
+    id TEXT PRIMARY KEY,
+    connection_id TEXT NOT NULL,
+    group_id TEXT NOT NULL,
+    name TEXT NOT NULL,
+    description TEXT,
+    content TEXT,
+    favorite INTEGER DEFAULT 0,
+    language TEXT DEFAULT '',
+    metadata TEXT DEFAULT '{}', -- JSON object as TEXT
+    tags TEXT DEFAULT '[]',     -- JSON array as TEXT
+    enabled INTEGER DEFAULT 1,
+    owner TEXT DEFAULT '',
+    checksum TEXT DEFAULT '',
+    execution_count INTEGER DEFAULT 0,
+    last_executed TEXT,
+    last_run_status TEXT DEFAULT '',
+    created_at DATETIME,
+    updated_at DATETIME
+);
+
+-- Create index for lookup by connection
+CREATE INDEX IF NOT EXISTS idx_scripts_connection_id ON scripts(connection_id);
+CREATE INDEX IF NOT EXISTS idx_scripts_group_id ON scripts(group_id);
+
+COMMIT;
+PRAGMA foreign_keys = ON;

BIN
DBconfig/storage.db


+ 125 - 0
app.log

@@ -1294,3 +1294,128 @@
 {"level":"info","timestamp":"2025-11-19T15:42:33.506+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
 {"level":"info","timestamp":"2025-11-19T15:42:33.506+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
 {"level":"info","timestamp":"2025-11-19T15:42:33.507+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-20T17:28:12.466+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-20T17:28:12.475+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-20T17:28:12.478+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-20T17:28:12.478+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-20T17:28:12.479+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-20T17:32:02.984+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-20T17:32:02.985+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-20T17:32:02.989+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-20T17:32:02.989+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-20T17:32:02.989+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-20T17:39:22.485+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-20T17:39:22.496+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-20T17:39:22.499+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-20T17:39:22.499+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-20T17:39:22.499+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-20T17:47:34.060+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-20T17:47:34.074+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-20T17:47:34.078+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-20T17:47:34.078+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-20T17:47:34.078+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T14:09:58.226+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T14:09:58.227+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T14:09:58.257+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T14:09:58.257+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T14:09:58.257+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T14:43:43.719+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T14:43:43.729+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T14:43:43.734+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T14:43:43.734+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T14:43:43.734+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T14:48:15.158+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T14:48:15.173+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T14:48:15.173+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T14:48:15.173+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T14:48:15.173+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T15:06:01.280+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T15:06:01.291+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T15:06:01.296+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T15:06:01.296+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T15:06:01.296+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T15:09:08.009+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T15:09:08.019+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T15:09:08.024+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T15:09:08.024+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T15:09:08.024+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T15:31:31.270+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T15:31:31.281+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T15:31:31.285+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T15:31:31.285+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T15:31:31.285+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T15:36:26.691+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T15:36:26.702+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T15:36:26.706+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T15:36:26.706+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T15:36:26.706+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T15:56:18.141+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T15:56:18.170+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T15:56:18.174+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T15:56:18.174+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T15:56:18.174+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T16:17:58.473+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T16:17:58.488+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T16:17:58.488+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T16:17:58.488+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T16:17:58.488+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T16:38:46.994+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T16:38:47.004+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T16:38:47.009+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T16:38:47.009+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T16:38:47.009+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T16:39:19.097+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T16:39:19.107+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T16:39:19.111+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T16:39:19.112+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T16:39:19.112+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T16:44:39.502+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T16:44:39.536+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T16:44:39.538+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T16:44:39.538+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T16:44:39.538+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T16:48:51.508+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T16:48:51.539+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T16:48:51.540+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T16:48:51.540+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T16:48:51.540+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T17:03:47.195+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T17:03:47.210+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T17:03:47.210+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T17:03:47.210+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T17:03:47.210+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T17:10:27.335+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T17:10:27.352+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T17:10:27.353+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T17:10:27.353+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T17:10:27.353+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T17:20:37.020+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T17:20:37.034+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T17:20:37.034+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T17:20:37.034+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T17:20:37.034+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-21T17:30:38.290+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-21T17:30:38.323+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-21T17:30:38.324+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-21T17:30:38.324+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-21T17:30:38.324+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-24T09:44:44.054+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-24T09:44:44.066+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-24T09:44:44.071+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-24T09:44:44.071+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-24T09:44:44.072+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-24T09:47:50.665+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-24T09:47:50.675+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-24T09:47:50.680+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-24T09:47:50.680+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-24T09:47:50.680+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-24T10:04:33.031+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-24T10:04:33.041+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-24T10:04:33.046+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-24T10:04:33.046+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-24T10:04:33.046+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}
+{"level":"info","timestamp":"2025-11-24T10:28:51.503+0800","caller":"logger/logger.go:104","message":"数据库连接池初始化完成"}
+{"level":"info","timestamp":"2025-11-24T10:28:51.529+0800","caller":"logger/logger.go:104","message":"应用初始化完成","config_path":"config.toml","audit_enabled":true,"storage_type":"db"}
+{"level":"info","timestamp":"2025-11-24T10:28:51.534+0800","caller":"logger/logger.go:104","message":"路由注册完成"}
+{"level":"info","timestamp":"2025-11-24T10:28:51.535+0800","caller":"logger/logger.go:104","message":"HTTP服务器设置完成","addr":"127.0.0.1","cors_enabled":true,"audit_enabled":true}
+{"level":"info","timestamp":"2025-11-24T10:28:51.535+0800","caller":"logger/logger.go:104","message":"HTTP服务器启动完成","IP:Port":"127.0.0.1:8080"}

+ 91 - 5
service/internal/common/databases/drivers/mysql/v8/meta.go

@@ -7,6 +7,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"reflect"
+	"sort"
 	"strings"
 	"time"
 )
@@ -1092,6 +1093,91 @@ func (q *MySQLDriver) DescribeCreateTemplate(ctx context.Context, path meta.Obje
 	}
 }
 
+// GetMetadataInfo 返回数据库的元信息(关键字、字段类型、能力等)
+func (q *MySQLDriver) GetMetadataInfo(ctx context.Context) (meta.MetadataCapabilities, error) {
+	var caps meta.MetadataCapabilities
+
+	// 1) 尝试从 INFORMATION_SCHEMA.COLUMNS 获取字段类型
+	rows, err := q.db.QueryContext(ctx, "SELECT DISTINCT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS")
+	if err == nil {
+		defer rows.Close()
+		for rows.Next() {
+			var dt sql.NullString
+			if err := rows.Scan(&dt); err != nil {
+				continue
+			}
+			if dt.Valid && dt.String != "" {
+				caps.FieldTypes = append(caps.FieldTypes, strings.ToUpper(dt.String))
+			}
+		}
+	} else {
+		// 回退到一组常见类型
+		caps.FieldTypes = []string{"INT", "BIGINT", "VARCHAR", "TEXT", "DATETIME", "TIMESTAMP", "DATE", "CHAR", "FLOAT", "DOUBLE", "DECIMAL", "BOOLEAN"}
+	}
+
+	// 2) 尝试从 mysql.help_keyword 获取关键字(部分 MySQL 安装提供)
+	rows2, err2 := q.db.QueryContext(ctx, "SELECT DISTINCT word FROM mysql.help_keyword")
+	if err2 == nil {
+		defer rows2.Close()
+		for rows2.Next() {
+			var kw sql.NullString
+			if err := rows2.Scan(&kw); err != nil {
+				continue
+			}
+			if kw.Valid && kw.String != "" {
+				caps.Keywords = append(caps.Keywords, kw.String)
+			}
+		}
+	} else {
+		// 3) 尝试 INFORMATION_SCHEMA.KEYWORDS(部分版本可用)
+		rows3, err3 := q.db.QueryContext(ctx, "SELECT DISTINCT keyword FROM INFORMATION_SCHEMA.KEYWORDS")
+		if err3 == nil {
+			defer rows3.Close()
+			for rows3.Next() {
+				var kw sql.NullString
+				if err := rows3.Scan(&kw); err != nil {
+					continue
+				}
+				if kw.Valid && kw.String != "" {
+					caps.Keywords = append(caps.Keywords, kw.String)
+				}
+			}
+		}
+	}
+
+	// 4) 去重与排序
+	if len(caps.FieldTypes) > 0 {
+		m := map[string]struct{}{}
+		for _, v := range caps.FieldTypes {
+			m[v] = struct{}{}
+		}
+		caps.FieldTypes = caps.FieldTypes[:0]
+		for k := range m {
+			caps.FieldTypes = append(caps.FieldTypes, k)
+		}
+		sort.Strings(caps.FieldTypes)
+	}
+	if len(caps.Keywords) > 0 {
+		m := map[string]struct{}{}
+		for _, v := range caps.Keywords {
+			m[v] = struct{}{}
+		}
+		caps.Keywords = caps.Keywords[:0]
+		for k := range m {
+			caps.Keywords = append(caps.Keywords, k)
+		}
+		sort.Strings(caps.Keywords)
+	}
+
+	// 5) 常见能力标记
+	caps.Capabilities = map[string]bool{
+		"supportsTransactions":   true,
+		"supportsDDLTransaction": false,
+	}
+
+	return caps, nil
+}
+
 // CreateObject 仅实现 preview(execute==false)路径:校验输入并生成 SQL,execute=true 尚未实现
 func (q *MySQLDriver) CreateObject(ctx context.Context, req meta.CreateObjectRequest) (meta.CreateObjectResponse, error) {
 	var resp meta.CreateObjectResponse
@@ -1282,17 +1368,17 @@ func (q *MySQLDriver) QueryData(ctx context.Context, path meta.ObjectPath, req m
 		}
 		defer rows.Close()
 		for rows.Next() {
-			var colName, dataType, nullable string
+			var colName, dataType, nullable sql.NullString
 			if err := rows.Scan(&colName, &dataType, &nullable); err != nil {
 				continue
 			}
 			columns = append(columns, meta.DataMeta{
-				Name:     colName,
+				Name:     colName.String,
 				Type:     "column",
-				DBType:   dataType,
-				Nullable: nullable == "YES",
+				DBType:   dataType.String,
+				Nullable: nullable.String == "YES",
 			})
-			columnNames = append(columnNames, colName)
+			columnNames = append(columnNames, colName.String)
 		}
 	} else {
 		// 指定列

+ 34 - 0
service/internal/common/databases/drivers/resources.go

@@ -0,0 +1,34 @@
+package drivers
+
+// DriverResource 描述驱动的可展示资源(如 logo、显示名等)
+type DriverResource struct {
+	DBType      string `json:"dbType"`
+	Version     string `json:"version"`
+	DisplayName string `json:"displayName,omitempty"`
+	// LogoPath 为可通过静态服务器访问的相对路径,例如 "/static/drivers/mysql/v8/logo.png"
+	LogoPath string            `json:"logoPath,omitempty"`
+	Extras   map[string]string `json:"extras,omitempty"`
+}
+
+var resources = map[string]map[string]DriverResource{} // dbType -> version -> resource
+
+// RegisterDriverResource 注册驱动资源信息,通常在驱动 init 中调用。
+func RegisterDriverResource(dbType, version string, res DriverResource) {
+	if _, ok := resources[dbType]; !ok {
+		resources[dbType] = map[string]DriverResource{}
+	}
+	resources[dbType][version] = res
+}
+
+// GetDriverResources 返回已注册的驱动资源列表(去平铺)
+func GetDriverResources() []DriverResource {
+	out := []DriverResource{}
+	for dbt, versions := range resources {
+		for ver, r := range versions {
+			r.DBType = dbt
+			r.Version = ver
+			out = append(out, r)
+		}
+	}
+	return out
+}

+ 8 - 0
service/internal/common/databases/drivers/resources/README.md

@@ -0,0 +1,8 @@
+Place driver resource files here.
+
+Recommended structure:
+- service/internal/common/databases/drivers/resources/mysql/v8/logo.png
+- service/internal/common/databases/drivers/resources/postgres/v13/logo.png
+
+The server exposes these files under `/static/drivers/<dbtype>/<version>/logo.png`.
+Drivers can register resource metadata using `drivers.RegisterDriverResource` to expose `LogoPath` and display name.

+ 8 - 0
service/internal/common/databases/interfaces.go

@@ -91,6 +91,14 @@ type MetadataRefresher interface {
 	RefreshObject(ctx context.Context, objectID string) error
 }
 
+// MetadataInfo 提供数据库级别的额外元信息查询,例如数据库支持的关键字列表
+type MetadataInfo interface {
+	// GetMetadataInfo 返回目标数据库的元信息(例如支持的关键字、字段类型等),供语法高亮/自动完成和能力检测使用。
+	// - ctx: 上下文控制
+	// 注意:驱动层通常不需要传入 connID;实现类应自行管理连接上下文。
+	GetMetadataInfo(ctx context.Context) (meta.MetadataCapabilities, error)
+}
+
 // DataReader 数据读取接口:对路径指向的对象(通常为 table / view)执行数据查询
 // 设计原则:大道至简 + 可增量扩展。保持单方法 + 请求对象聚合参数。
 // 必选参数:

+ 21 - 1
service/internal/common/databases/meta/meta.go

@@ -23,7 +23,7 @@ type GenericObject struct {
 	Description string            `json:"description,omitempty"` // 简短描述,用于 UI 展示或类型占位说明
 	Type        string            `json:"type"`                  // 对象类型(table、column等)
 	ParentID    string            `json:"parentId"`              // 父对象ID
-	DBType      string            `json:"dbType"`                // 数据库类型(mysql、oracle等)
+	DBType      string            `json:"dbType"`                // 数据库类型(mysql、oracle等)。保留字段名 `DBType` 用于驱动/元数据定位,注意该字段与对象的 `Type` 字段语义不同。
 	Attrs       map[string]string `json:"attrs"`                 // 动态属性(统一为 string 以避免 interface{})
 	Children    []GenericObject   `json:"children"`              // 子对象
 }
@@ -164,3 +164,23 @@ type UpdateObjectResponse struct {
 // 	CommonAttrs []AttrMeta            `json:"commonAttrs"` // 通用属性
 // 	DBAttrs     map[string][]AttrMeta `json:"dbAttrs"`     // 数据库特有属性(key: dbType)
 // }
+
+// MetadataCapabilities 是更通用的元信息描述,适用于任意数据引擎或数据源。
+// 建议驱动实现该结构以报告引擎能力、关键字、数据类型及其他可选能力。
+type MetadataCapabilities struct {
+
+	// Keywords 支持的关键字/保留字列表
+	Keywords []string `json:"keywords,omitempty"`
+
+	// FieldTypes 支持的数据类型列表(规范化为大写,例如 INT, BIGINT, VARCHAR)
+	FieldTypes []string `json:"fieldTypes,omitempty"`
+
+	// TypeAliases 可选:将逻辑类型映射到底层类型集合,例如 {"string": ["VARCHAR","TEXT"]}
+	TypeAliases map[string][]string `json:"typeAliases,omitempty"`
+
+	// Capabilities 用于描述可选能力的布尔标志(例如 supportsTransactions、supportsDDLTransaction)
+	Capabilities map[string]bool `json:"capabilities,omitempty"`
+
+	// Functions 列表(可选):内置函数/聚合等,供自动完成或能力检测使用
+	Functions []string `json:"functions,omitempty"`
+}

+ 17 - 17
service/internal/common/manager/connection/connection_pool.go

@@ -14,11 +14,11 @@ import (
 
 // PooledConnection 池化连接,包装数据库连接和元数据
 type PooledConnection struct {
-	DbConn    databases.Connection   // 数据库连接
-	LastUsed  time.Time              // 最后使用时间
-	DBType    string                 // 数据库类型
-	DbVersion string                 // 数据库版本
-	DbConfig  *meta.ConnectionConfig // 连接配置
+	DbConn   databases.Connection   // 数据库连接
+	LastUsed time.Time              // 最后使用时间
+	Type     string                 // 数据库类型
+	Version  string                 // 数据库版本
+	DbConfig *meta.ConnectionConfig // 连接配置
 
 	closeChan chan struct{}      // 关闭通知通道
 	ctx       context.Context    // 上下文
@@ -37,8 +37,8 @@ type ConnectionPool struct {
 // PoolStats 描述连接池中单个连接的运行状态快照
 type PoolStats struct {
 	LastUsed     time.Time
-	DBType       string
-	DBVersion    string
+	Type         string
+	Version      string
 	IsConnected  bool
 	MaxIdleTime  time.Duration
 	MaxLifetime  time.Duration
@@ -62,7 +62,7 @@ func NewConnectionPool(ctx context.Context) *ConnectionPool {
 }
 
 // RegisterConnectionWithConfig 使用新的配置结构注册连接
-func (cp *ConnectionPool) RegisterConnectionWithConfig(connID string, dbType, DbVersion string, config *meta.ConnectionConfig) error {
+func (cp *ConnectionPool) RegisterConnectionWithConfig(connID string, dbType, dbVersion string, config *meta.ConnectionConfig) error {
 	cp.mu.Lock()
 	defer cp.mu.Unlock()
 
@@ -74,7 +74,7 @@ func (cp *ConnectionPool) RegisterConnectionWithConfig(connID string, dbType, Db
 	ctx, cancel := context.WithCancel(cp.cleanupCtx)
 
 	// 立即创建数据库执行器
-	executor, err := drivers.Connect(dbType, DbVersion, config)
+	executor, err := drivers.Connect(dbType, dbVersion, config)
 	if err != nil {
 		cancel()
 		return fmt.Errorf("创建数据库连接失败: %v", err)
@@ -90,8 +90,8 @@ func (cp *ConnectionPool) RegisterConnectionWithConfig(connID string, dbType, Db
 	cp.pools[connID] = &PooledConnection{
 		DbConn:    executor,
 		LastUsed:  time.Now(),
-		DBType:    dbType,
-		DbVersion: DbVersion,
+		Type:      dbType,
+		Version:   dbVersion,
 		DbConfig:  config,
 		closeChan: make(chan struct{}),
 		ctx:       ctx,
@@ -161,8 +161,8 @@ func (cp *ConnectionPool) RegisterConnection(
 	cp.pools[connID] = &PooledConnection{
 		DbConn:    executor,
 		LastUsed:  time.Now(),
-		DBType:    dbType,
-		DbVersion: dbVersion,
+		Type:      dbType,
+		Version:   dbVersion,
 		DbConfig:  config,
 		closeChan: make(chan struct{}),
 		ctx:       ctx,
@@ -198,7 +198,7 @@ func (cp *ConnectionPool) GetConnection(connID string) (*PooledConnection, error
 		// 再次检查是否已初始化(并发情况下)
 		if pooledConn.DbConn == nil {
 			// 创建新执行器
-			executor, err := drivers.Connect(pooledConn.DBType, pooledConn.DbVersion, pooledConn.DbConfig)
+			executor, err := drivers.Connect(pooledConn.Type, pooledConn.Version, pooledConn.DbConfig)
 			if err != nil {
 				return nil, fmt.Errorf("创建数据库执行器失败: %v", err)
 			}
@@ -227,7 +227,7 @@ func (cp *ConnectionPool) GetConnection(connID string) (*PooledConnection, error
 			pooledConn.DbConn.Close()
 
 			// 创建新执行器
-			executor, err := drivers.Connect(pooledConn.DBType, pooledConn.DbVersion, pooledConn.DbConfig)
+			executor, err := drivers.Connect(pooledConn.Type, pooledConn.Version, pooledConn.DbConfig)
 			if err != nil {
 				return nil, fmt.Errorf("创建数据库执行器失败: %v", err)
 			}
@@ -378,8 +378,8 @@ func (cp *ConnectionPool) buildPoolStatsLocked(pooledConn *PooledConnection) Poo
 
 	return PoolStats{
 		LastUsed:     pooledConn.LastUsed,
-		DBType:       pooledConn.DBType,
-		DBVersion:    pooledConn.DbVersion,
+		Type:         pooledConn.Type,
+		Version:      pooledConn.Version,
 		IsConnected:  pooledConn.DbConn != nil,
 		MaxIdleTime:  maxIdleTime,
 		MaxLifetime:  maxLifetime,

+ 115 - 111
service/internal/common/manager/storage/README.md

@@ -1,17 +1,17 @@
 # 存储管理器
 
-这个包提供了两种存储方式来管理数据库连接、文件夹和SQL文件
+这个包提供了两种存储方式来管理数据库连接、文件夹和脚本(Script)
 
 ## 存储方式
 
 ### 1. 文件存储 (file_storage)
 - 使用TOML文件存储配置信息
-- SQL文件内容存储在文件系统中
+- 脚本内容存储在文件系统中(`sql_base_dir`)
 - 适合单用户、小型应用
 
 ### 2. 数据库存储 (db_storage)
 - 使用SQLite数据库存储所有数据
-- 所有信息(配置、SQL文件内容)都存储在数据库中
+- 所有信息(配置、脚本内容)都存储在数据库中
 - 适合多用户、需要并发访问的应用
 
 ## 接口定义
@@ -22,32 +22,34 @@
 type StorageInterface interface {
     // ========== 连接管理方法 ==========
     
-    // CreateConnection 在指定文件夹下创建新的数据库连接
+    // CreateConnection 在指定文件夹下创建新的连接(支持多种连接类型)
     // 参数:
     //   - folderPath: 连接存放的文件夹路径
     //   - name: 连接名称
     //   - description: 连接描述
-    //   - dbType: 数据库类型 (mysql, postgresql, sqlite等)
-    //   - dbVersion: 数据库版本
-    //   - host: 数据库主机地址
-    //   - port: 数据库端口
+    //   - kind: 连接类别,示例值为 `database` / `server` / `other`
+    //   - type: 具体类型或子类型,例如针对数据库为 `mysql`/`postgres`,针对服务器可能为 `ssh`
+    //   - version: 版本号或次级信息(例如 `8.0`)
+    //   - host: 数据库/服务器主机地址
+    //   - port: 网络端口
     //   - username: 用户名
     //   - password: 密码
-    //   - database: 数据库名
+    //   - database: 数据库名(当 kind=="database" 时适用)
     //   - connStr: 自定义连接字符串 (可选)
     //   - tags: 标签列表
     // 返回: 创建的连接对象或错误
-    CreateConnection(folderPath, name, description, dbType, dbVersion, host string, port int, username, password, database, connStr string, tags []string) (*types.DBConnection, error)
+    // CreateConnection: 使用 `type`/`version`(已移除旧字段 `db_type`/`db_version`)
+    CreateConnection(folderPath, name, description, typ, version, host string, port int, username, password, database, connStr string, tags []string) (*types.ConnectionWithDetails, error)
     
     // GetConnection 根据连接ID获取连接详细信息
     // 参数: connID - 连接的唯一标识符
     // 返回: 连接对象或错误 (如果连接不存在)
-    GetConnection(connID string) (*types.DBConnection, error)
+    GetConnection(connID string) (*types.ConnectionWithDetails, error)
     
     // UpdateConnection 更新现有连接的信息
     // 参数: connID - 要更新的连接ID, 其他参数同CreateConnection
     // 返回: 更新后的连接对象或错误
-    UpdateConnection(connID, name, description, dbType, dbVersion, host string, port int, username, password, database, connStr string, tags []string) (*types.DBConnection, error)
+    UpdateConnection(connID, name, description, typ, version, host string, port int, username, password, database, connStr string, tags []string) (*types.ConnectionWithDetails, error)
     
     // DeleteConnection 删除指定文件夹下的连接
     // 参数:
@@ -61,15 +63,15 @@ type StorageInterface interface {
     //   - connID: 要移动的连接ID
     //   - targetPath: 目标文件夹路径
     // 返回: 移动后的连接对象或错误
-    MoveConnection(connID, targetPath string) (*types.DBConnection, error)
+    MoveConnection(connID, targetPath string) (*types.ConnectionWithDetails, error)
     
     // GetRootConnections 获取根目录 ("/") 下的所有连接
     // 返回: 根目录连接列表或错误
-    GetRootConnections() ([]types.DBConnection, error)
+    GetRootConnections() ([]types.ConnectionWithDetails, error)
     
     // GetAllConnections 获取所有连接 (包括子文件夹中的)
     // 返回: 所有连接的完整列表或错误
-    GetAllConnections() ([]types.DBConnection, error)
+    GetAllConnections() ([]types.ConnectionWithDetails, error)
 
     // ========== 文件夹管理方法 ==========
     
@@ -110,49 +112,49 @@ type StorageInterface interface {
     // 返回: 存在(true)/不存在(false) 和可能的错误
     FolderExists(path string) (bool, error)
 
-    // ========== SQL文件管理方法 ==========
+    // ========== 脚本 (Script) 管理方法 ==========
     
-    // CreateSQLFile 为指定连接创建SQL文件
+    // CreateScript 为指定连接创建脚本记录
     // 参数:
     //   - connID: 关联的数据库连接ID
-    //   - name: SQL文件名
-    //   - description: 文件描述
+    //   - name: 脚本名称
+    //   - description: 脚本描述
     //   - tags: 标签列表
-    //   - content: SQL文件内容
-    // 返回: 创建的SQL文件对象或错误
-    CreateSQLFile(connID, name, description string, tags []string, content string) (*types.SQLFile, error)
+    //   - content: 脚本内容 (SQL 或其它语言取决于 `language` 字段)
+    // 返回: 创建的 Script 对象或错误
+    CreateScript(connID, name, description string, tags []string, content string) (*types.Script, error)
     
-    // GetSQLFile 获取SQL文件信息
-    // 参数: fileID - SQL文件的唯一标识符
-    // 返回: SQL文件对象或错误
-    GetSQLFile(fileID string) (*types.SQLFile, error)
+    // GetScript 获取脚本信息
+    // 参数: scriptID - 脚本的唯一标识符
+    // 返回: Script 对象或错误
+    GetScript(scriptID string) (*types.Script, error)
     
-    // UpdateSQLFile 更新SQL文件信息 (不包含内容)
-    // 参数: fileID - 文件ID, 其他参数同CreateSQLFile (除content外)
-    // 返回: 更新后的SQL文件对象或错误
-    UpdateSQLFile(fileID, name, description string, tags []string) (*types.SQLFile, error)
+    // UpdateScript 更新脚本信息 (不包含内容)
+    // 参数: scriptID - 脚本ID, 其他参数同CreateScript (除content外)
+    // 返回: 更新后的 Script 对象或错误
+    UpdateScript(scriptID, name, description string, tags []string) (*types.Script, error)
     
-    // DeleteSQLFile 删除SQL文件
-    // 参数: fileID - 要删除的文件ID
+    // DeleteScript 删除脚本
+    // 参数: scriptID - 要删除的脚本ID
     // 返回: 错误 (如果删除失败)
-    DeleteSQLFile(fileID string) error
+    DeleteScript(scriptID string) error
     
-    // ReadSQLFileContent 读取SQL文件内容
-    // 参数: fileID - 文件ID
-    // 返回: 文件内容字符串或错误
-    ReadSQLFileContent(fileID string) (string, error)
+    // ReadScriptContent 读取脚本内容
+    // 参数: scriptID - 脚本ID
+    // 返回: 脚本内容字符串或错误
+    ReadScriptContent(scriptID string) (string, error)
     
-    // WriteSQLFileContent 写入SQL文件内容
+    // WriteScriptContent 写入脚本内容
     // 参数:
-    //   - fileID: 文件ID
+    //   - scriptID: 脚本ID
     //   - content: 要写入的内容
     // 返回: 错误 (如果写入失败)
-    WriteSQLFileContent(fileID, content string) error
+    WriteScriptContent(scriptID, content string) error
     
-    // ListSQLFiles 列出指定连接的所有SQL文件
+    // ListScripts 列出指定连接的所有脚本
     // 参数: connID - 数据库连接ID
-    // 返回: SQL文件列表或错误
-    ListSQLFiles(connID string) ([]types.SQLFile, error)
+    // 返回: Script 列表或错误
+    ListScripts(connID string) ([]types.Script, error)
 
     // ========== 配置管理方法 ==========
     
@@ -163,7 +165,7 @@ type StorageInterface interface {
     // SaveConnection 保存连接配置
     // 参数: conn - 要保存的连接对象
     // 返回: 错误 (如果保存失败)
-    SaveConnection(conn *types.DBConnection) error
+    SaveConnection(conn *types.ConnectionWithDetails) error
 }
 ```
 
@@ -179,21 +181,21 @@ import "dbview/service/internal/common/manager/storage"
 // 创建文件存储管理器
 // 参数:
 //   - configPath: TOML配置文件路径 (如 "data.toml")
-//   - sqlBaseDir: SQL文件存储的基础目录 (如 "sql_files")
-manager, err := storage.NewFileStorage("data.toml", "sql_files")
+//   - sqlBaseDir: 脚本存储的基础目录 (如 "sql_files")
+manager, err := storage.NewFileStorage("data.toml", "scripts")
 if err != nil {
     log.Fatal(err) // 创建失败时程序退出
 }
 
 // 使用manager进行操作
-// CreateConnection的参数说明:
+// CreateConnection 的参数说明:
 // - folderPath: 连接存放的文件夹路径 (如 "/")
 // - name: 连接名称 (如 "MyDB")
 // - description: 连接描述 (如 "My Database")
-// - dbType: 数据库类型 (如 "mysql")
-// - dbVersion: 数据库版本 (如 "8.0")
-// - host: 数据库主机 (如 "localhost")
-// - port: 数据库端口 (如 3306)
+// - type: 具体类型或子类型,例如针对数据库为 `mysql`/`postgres`,针对服务器可能为 `ssh`
+// - version: 版本号或次级信息(例如 `8.0`)
+// - host: 数据库/服务器主机地址 (如 "localhost")
+// - port: 网络端口 (如 3306)
 // - username: 用户名
 // - password: 密码
 // - database: 数据库名
@@ -221,6 +223,7 @@ if err != nil {
 
 // 使用manager进行操作(接口相同)
 // 所有方法的使用方式与文件存储完全相同
+// 注意:接口参数使用 `type`/`version` 表示连接类型与次级版本信息
 conn, err := manager.CreateConnection("/", "MyDB", "My Database", "mysql", "8.0", "localhost", 3306, "user", "pass", "mydb", "", []string{"dev"})
 if err != nil {
     log.Printf("创建连接失败: %v", err)
@@ -238,8 +241,8 @@ import "dbview/service/internal/common/manager/storage"
 // GetStorage的参数:
 //   - storageType: 存储类型 (storage.FileStorage 或 storage.DBStorage)
 //   - configPath: 配置文件路径 (文件存储) 或 空字符串 (数据库存储)
-//   - dbPath: 数据库路径 (数据库存储) 或 SQL文件目录 (文件存储)
-manager, err := storage.GetStorage(storage.FileStorage, "data.toml", "sql_files")
+//   - dbPath: 数据库路径 (数据库存储) 或 脚本目录 (文件存储)
+manager, err := storage.GetStorage(storage.FileStorage, "data.toml", "scripts")
 if err != nil {
     log.Fatal("创建文件存储管理器失败:", err)
 }
@@ -262,7 +265,7 @@ if err != nil {
 
 数据库连接管理提供了完整的CRUD操作,以及连接的组织和移动功能。
 
-- `CreateConnection(folderPath, name, description, dbType, dbVersion, host, port, username, password, database, connStr, tags)` 
+- `CreateConnection(folderPath, name, description, type, version, host, port, username, password, database, connStr, tags)` 
   - **功能**: 在指定文件夹下创建新的数据库连接配置
   - **参数**: 文件夹路径、连接基本信息、数据库连接参数、标签
   - **返回值**: 创建成功的连接对象,包含自动生成的ID和时间戳
@@ -274,14 +277,14 @@ if err != nil {
   - **返回值**: 包含所有连接详情的对象
   - **错误**: 连接不存在
 
-- `UpdateConnection(connID, name, description, dbType, dbVersion, host, port, username, password, database, connStr, tags)` 
+- `UpdateConnection(connID, name, description, type, version, host, port, username, password, database, connStr, tags)` 
   - **功能**: 更新现有连接的配置信息
   - **参数**: 连接ID和新配置信息
   - **返回值**: 更新后的连接对象
   - **错误**: 连接不存在、参数验证失败
 
 - `DeleteConnection(folderPath, connID)` 
-  - **功能**: 删除指定文件夹下的连接及其关联的SQL文件
+  - **功能**: 删除指定文件夹下的连接及其关联的脚本(scripts)
   - **参数**: 文件夹路径和连接ID
   - **返回值**: 无
   - **错误**: 连接不存在、文件夹路径不匹配
@@ -327,7 +330,7 @@ if err != nil {
   - **错误**: 文件夹不存在
 
 - `DeleteFolder(path, name)`
-  - **功能**: 删除文件夹及其所有子文件夹、数据库连接和关联的SQL文件(级联删除)
+  - **功能**: 删除文件夹及其所有子文件夹、数据库连接和关联的脚本(级联删除)
   - **参数**:
     - `path`: 文件夹路径
     - `name`: 文件夹名称(用于验证)
@@ -349,50 +352,50 @@ if err != nil {
   - **返回值**: 布尔值表示是否存在
   - **错误**: 存储访问错误
 
-### SQL文件管理
+### 脚本 (Script) 管理
 
-SQL文件管理允许为每个数据库连接存储和管理SQL脚本
+脚本管理允许为每个数据库连接存储和管理可执行或保存的脚本(例如 SQL 语句片段)
 
-- `CreateSQLFile(connID, name, description, tags, content)` 
-  - **功能**: 为指定连接创建新的SQL文件
-  - **参数**: 连接ID、文件名、描述、标签、文件内容
-  - **返回值**: 创建成功的SQL文件对象
-  - **错误**: 连接不存在、文件名冲突
+- `CreateScript(connID, name, description, tags, content)` 
+  - **功能**: 为指定连接创建新的脚本记录
+  - **参数**: 连接ID、脚本名、描述、标签、脚本内容
+  - **返回值**: 创建成功的 Script 对象
+  - **错误**: 连接不存在、名冲突
 
-- `GetSQLFile(fileID)` 
-  - **功能**: 获取SQL文件的详细信息(不含内容)
-  - **参数**: 文件ID
-  - **返回值**: SQL文件对象
-  - **错误**: 文件不存在
+- `GetScript(scriptID)` 
+  - **功能**: 获取脚本的详细信息(不含内容)
+  - **参数**: 脚本ID
+  - **返回值**: Script 对象
+  - **错误**: 脚本不存在
 
-- `UpdateSQLFile(fileID, name, description, tags)` 
-  - **功能**: 更新SQL文件的基本信息(不含内容)
-  - **参数**: 文件ID和新信息
-  - **返回值**: 更新后的文件对象
-  - **错误**: 文件不存在
+- `UpdateScript(scriptID, name, description, tags)` 
+  - **功能**: 更新脚本的基本信息(不含内容)
+  - **参数**: 脚本ID和新信息
+  - **返回值**: 更新后的对象
+  - **错误**: 脚本不存在
 
-- `DeleteSQLFile(fileID)` 
-  - **功能**: 删除指定的SQL文件
-  - **参数**: 文件ID
+- `DeleteScript(scriptID)` 
+  - **功能**: 删除指定的脚本记录
+  - **参数**: 脚本ID
   - **返回值**: 无
-  - **错误**: 文件不存在
+  - **错误**: 脚本不存在
 
-- `ReadSQLFileContent(fileID)` 
-  - **功能**: 读取SQL文件的内容
-  - **参数**: 文件ID
-  - **返回值**: 文件内容字符串
-  - **错误**: 文件不存在、读取失败
+- `ReadScriptContent(scriptID)` 
+  - **功能**: 读取脚本的内容
+  - **参数**: 脚本ID
+  - **返回值**: 脚本内容字符串
+  - **错误**: 脚本不存在、读取失败
 
-- `WriteSQLFileContent(fileID, content)` 
-  - **功能**: 更新SQL文件的内容
-  - **参数**: 文件ID和新内容
+- `WriteScriptContent(scriptID, content)` 
+  - **功能**: 更新脚本的内容
+  - **参数**: 脚本ID和新内容
   - **返回值**: 无
-  - **错误**: 文件不存在、写入失败
+  - **错误**: 脚本不存在、写入失败
 
-- `ListSQLFiles(connID)` 
-  - **功能**: 列出指定连接的所有SQL文件
+- `ListScripts(connID)` 
+  - **功能**: 列出指定连接的所有脚本
   - **参数**: 连接ID
-  - **返回值**: SQL文件列表
+  - **返回值**: Script 列表
   - **错误**: 连接不存在
 
 ### 配置管理
@@ -422,8 +425,8 @@ type DBConnection struct {
     ID          string    `json:"id" db:"id"`          // 连接的唯一标识符 (UUID)
     Name        string    `json:"name" db:"name"`        // 连接显示名称
     Description string    `json:"description" db:"description"` // 连接描述信息
-    DBType      string    `json:"db_type" db:"db_type"` // 数据库类型: mysql, postgresql, sqlite等
-    DBVersion   string    `json:"db_version" db:"db_version"` // 数据库版本号
+    Type        string    `json:"type" db:"db_type"` // 数据库类型: mysql, postgresql, sqlite等
+    Version     string    `json:"version" db:"db_version"` // 数据库版本号
     Host        string    `json:"host" db:"host"`        // 数据库服务器主机地址
     Port        int       `json:"port" db:"port"`        // 数据库服务器端口号
     Username    string    `json:"username" db:"username"` // 数据库用户名
@@ -434,20 +437,20 @@ type DBConnection struct {
     LastUsed    time.Time `json:"last_used" db:"last_used"` // 最后使用时间
     CreatedTime time.Time `json:"created_time" db:"created_time"` // 创建时间
     Tags        []string  `json:"tags" db:"tags"`        // 标签列表,用于分类和搜索
-    SQLFiles    []SQLFile `json:"sql_files" db:"sql_files"` // 关联的SQL文件列表
+    Scripts    []Script `json:"scripts" db:"scripts"` // 关联的脚本列表
 }
 ```
 
 **字段说明**:
 - **ID**: 全局唯一标识符,用于在系统中唯一标识连接
 - **Name**: 用户友好的显示名称
-- **DBType/DBVersion**: 确定数据库驱动和兼容性
+- **Type/Version**: 确定数据库驱动和兼容性 (以前称为 `DBType`/`DBVersion`)
 - **Host/Port**: 网络连接信息
 - **Username/Password**: 认证信息
 - **ConnStr**: 高级用户可自定义完整连接字符串
 - **FolderPath**: 支持层次化组织连接
 - **Tags**: 支持多维度分类,如"dev"、"prod"、"test"等
-- **SQLFiles**: 关联的SQL脚本文件,便于管理
+- **Scripts**: 关联的脚本文件(Script),便于管理
 
 ### Folder - 文件夹
 
@@ -474,30 +477,31 @@ type Folder struct {
 - **Connections**: 文件夹直接包含的连接(不含子文件夹的连接)
 - **Tags**: 文件夹级别的分类标签
 
-### SQLFile - SQL文件
+### Script - 脚本
 
-SQL文件结构体定义了存储的SQL脚本信息
+脚本结构体示例,表示存储在系统中的可执行脚本或 SQL 片段
 
 ```go
-type SQLFile struct {
-    ID           string    `json:"id" db:"id"`           // 文件的唯一标识符
-    DBConnId     string    `json:"db_conn_id" db:"db_conn_id"` // 关联的数据库连接ID
-    Name         string    `json:"name" db:"name"`         // 文件名
-    Description  string    `json:"description" db:"description"` // 文件描述
-    Tags         []string  `json:"tags" db:"tags"`         // 标签列表
-    LastModified time.Time `json:"last_modified" db:"last_modified"` // 最后修改时间
-    CreatedTime  time.Time `json:"created_time" db:"created_time"` // 创建时间
-    Content      string    `json:"content" db:"content"`   // SQL文件内容
+type Script struct {
+  ID           string    `json:"id" db:"id"`
+  DBConnId     string    `json:"db_conn_id" db:"db_conn_id"` // 关联的数据库连接ID
+  Name         string    `json:"name" db:"name"`
+  Description  string    `json:"description" db:"description"`
+  Language     string    `json:"language" db:"language"` // 脚本语言,例如 "sql"/"plpgsql"/"python"
+  Tags         []string  `json:"tags" db:"tags"`
+  LastModified time.Time `json:"last_modified" db:"last_modified"`
+  CreatedTime  time.Time `json:"created_time" db:"created_time"`
+  Content      string    `json:"content" db:"content"`
 }
 ```
 
 **字段说明**:
-- **ID**: SQL文件唯一标识符
-- **DBConnId**: 关联的数据库连接,确保SQL脚本与正确数据库匹配
-- **Name**: SQL文件名,如"init.sql"、"query.sql"
-- **Content**: 实际的SQL脚本内容
+- **ID**: 脚本唯一标识符
+- **DBConnId**: 关联的数据库连接,确保脚本与正确数据库匹配
+- **Name**: 脚本名称
+- **Content**: 实际的脚本内容
 - **LastModified**: 内容最后修改时间,用于版本控制
-- **Tags**: SQL文件标签,如"ddl"、"dml"、"backup"等
+- **Tags**: 脚本标签,如"ddl"、"dml"、"maintenance"等
 
 ## 依赖
 

+ 160 - 113
service/internal/common/manager/storage/db_storage/connection_groups.go

@@ -4,6 +4,8 @@ import (
 	"database/sql"
 	"fmt"
 	"time"
+
+	"dbview/service/internal/common/manager/storage/types"
 )
 
 // CreateConnectionGroup 创建连接分组
@@ -24,16 +26,16 @@ func (sm *StorageManager) CreateConnectionGroup(parentID, name, description, ico
 		CreatedAt:    now,
 		UpdatedAt:    now,
 		Subgroups:    []ConnectionGroup{},
-		Connections:  []DBConnection{},
+		Connections:  []types.ConnectionWithDetails{},
 	}
 
-	sql := `
+	query := `
 		INSERT INTO connection_groups (
 			id, parent_id, name, description, 
 			display_order, icon, created_at, updated_at
 		) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
 
-	_, err := sm.db.Exec(sql,
+	_, err := sm.db.Exec(query,
 		id, parentID, name, description,
 		displayOrder, icon, now, now,
 	)
@@ -50,21 +52,21 @@ func (sm *StorageManager) GetConnectionGroup(groupID string, loadConnections boo
 	sm.mu.RLock()
 	defer sm.mu.RUnlock()
 
-	sql := `
+	query := `
 		SELECT id, parent_id, name, description, 
 			display_order, icon, created_at, updated_at
 		FROM connection_groups
 		WHERE id = ?`
 
-	row := sm.db.QueryRow(sql, groupID)
+	row := sm.db.QueryRow(query, groupID)
 	var group ConnectionGroup
 	var parentID *string // 对于根分组,parent_id 可能为 NULL
 	var icon *string     // 图标可能为 NULL
-	var createdAt, updatedAt []byte
+	var createdAtNull, updatedAtNull sql.NullString
 
 	err := row.Scan(
 		&group.ID, &parentID, &group.Name, &group.Description,
-		&group.DisplayOrder, &icon, &createdAt, &updatedAt,
+		&group.DisplayOrder, &icon, &createdAtNull, &updatedAtNull,
 	)
 
 	if err != nil {
@@ -83,9 +85,25 @@ func (sm *StorageManager) GetConnectionGroup(groupID string, loadConnections boo
 		group.Icon = "" // 如果为NULL,设为空字符串
 	}
 
-	// 转换时间
-	group.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", string(createdAt))
-	group.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", string(updatedAt))
+	// 解析时间
+	if createdAtNull.Valid {
+		if t, err := time.Parse(time.RFC3339Nano, createdAtNull.String); err == nil {
+			group.CreatedAt = t
+		} else if t2, err2 := time.Parse(time.RFC3339, createdAtNull.String); err2 == nil {
+			group.CreatedAt = t2
+		} else if t3, err3 := time.Parse("2006-01-02 15:04:05", createdAtNull.String); err3 == nil {
+			group.CreatedAt = t3
+		}
+	}
+	if updatedAtNull.Valid {
+		if t, err := time.Parse(time.RFC3339Nano, updatedAtNull.String); err == nil {
+			group.UpdatedAt = t
+		} else if t2, err2 := time.Parse(time.RFC3339, updatedAtNull.String); err2 == nil {
+			group.UpdatedAt = t2
+		} else if t3, err3 := time.Parse("2006-01-02 15:04:05", updatedAtNull.String); err3 == nil {
+			group.UpdatedAt = t3
+		}
+	}
 
 	// 如果需要加载子分组(递归)
 	if recursive {
@@ -110,14 +128,14 @@ func (sm *StorageManager) GetConnectionGroup(groupID string, loadConnections boo
 
 // getSubgroups 获取子分组
 func (sm *StorageManager) getSubgroups(parentID string, loadConnections bool, recursive bool) ([]ConnectionGroup, error) {
-	sql := `
+	query := `
 		SELECT id, parent_id, name, description, 
 			display_order, icon, created_at, updated_at
 		FROM connection_groups
 		WHERE parent_id = ?
 		ORDER BY display_order, name`
 
-	rows, err := sm.db.Query(sql, parentID)
+	rows, err := sm.db.Query(query, parentID)
 	if err != nil {
 		return nil, fmt.Errorf("查询子分组失败,父分组ID: %s,错误: %v", parentID, err)
 	}
@@ -128,11 +146,11 @@ func (sm *StorageManager) getSubgroups(parentID string, loadConnections bool, re
 		var group ConnectionGroup
 		var groupParentID *string // 对于根分组,parent_id 可能为 NULL
 		var icon *string          // 图标可能为 NULL
-		var createdAt, updatedAt []byte
+		var createdAtNull, updatedAtNull sql.NullString
 
 		err := rows.Scan(
 			&group.ID, &groupParentID, &group.Name, &group.Description,
-			&group.DisplayOrder, &icon, &createdAt, &updatedAt,
+			&group.DisplayOrder, &icon, &createdAtNull, &updatedAtNull,
 		)
 
 		if err != nil {
@@ -151,9 +169,25 @@ func (sm *StorageManager) getSubgroups(parentID string, loadConnections bool, re
 			group.Icon = "" // 如果为NULL,设为空字符串
 		}
 
-		// 转换时间
-		group.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", string(createdAt))
-		group.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", string(updatedAt))
+		// 解析时间
+		if createdAtNull.Valid {
+			if t, err := time.Parse(time.RFC3339Nano, createdAtNull.String); err == nil {
+				group.CreatedAt = t
+			} else if t2, err2 := time.Parse(time.RFC3339, createdAtNull.String); err2 == nil {
+				group.CreatedAt = t2
+			} else if t3, err3 := time.Parse("2006-01-02 15:04:05", createdAtNull.String); err3 == nil {
+				group.CreatedAt = t3
+			}
+		}
+		if updatedAtNull.Valid {
+			if t, err := time.Parse(time.RFC3339Nano, updatedAtNull.String); err == nil {
+				group.UpdatedAt = t
+			} else if t2, err2 := time.Parse(time.RFC3339, updatedAtNull.String); err2 == nil {
+				group.UpdatedAt = t2
+			} else if t3, err3 := time.Parse("2006-01-02 15:04:05", updatedAtNull.String); err3 == nil {
+				group.UpdatedAt = t3
+			}
+		}
 
 		// 如果需要递归加载子分组
 		if recursive {
@@ -184,48 +218,121 @@ func (sm *StorageManager) getSubgroups(parentID string, loadConnections bool, re
 }
 
 // getConnectionsByGroup 获取分组下的连接
-func (sm *StorageManager) getConnectionsByGroup(groupID string) ([]DBConnection, error) {
-	sql := `SELECT 
-		id, group_id, name, description, db_type, db_version, 
-		server, port, database, username, password_encrypted, 
-		use_ssh_tunnel, connection_string, color, last_connected, 
-		auto_connect, display_order, created_at, updated_at
+func (sm *StorageManager) getConnectionsByGroup(groupID string) ([]types.ConnectionWithDetails, error) {
+	query := `SELECT 
+		id, group_id, name, description, kind, color, auto_connect, display_order, created_at, updated_at
 	FROM connections 
 	WHERE group_id = ?
 	ORDER BY display_order, name`
 
-	rows, err := sm.db.Query(sql, groupID)
+	rows, err := sm.db.Query(query, groupID)
 	if err != nil {
 		return nil, fmt.Errorf("查询分组连接失败,分组ID: %s,错误: %v", groupID, err)
 	}
 	defer rows.Close()
 
-	var connections []DBConnection
+	var connections []types.ConnectionWithDetails
 	for rows.Next() {
-		var conn DBConnection
-		var useSSHTunnel, autoConnect int
-		var lastConnected, createdAt, updatedAt []byte
-
-		err := rows.Scan(
-			&conn.ID, &conn.GroupID, &conn.Name, &conn.Description,
-			&conn.DBType, &conn.DBVersion, &conn.Server, &conn.Port,
-			&conn.Database, &conn.Username, &conn.Password,
-			&useSSHTunnel, &conn.ConnectionString, &conn.Color, &lastConnected,
-			&autoConnect, &conn.DisplayOrder, &createdAt, &updatedAt,
-		)
+		var id, gid, name, description, kind, color string
+		var autoConnectInt, displayOrder int
+		var createdAtNull, updatedAtNull sql.NullString
 
+		err := rows.Scan(&id, &gid, &name, &description, &kind, &color, &autoConnectInt, &displayOrder, &createdAtNull, &updatedAtNull)
 		if err != nil {
 			return nil, fmt.Errorf("扫描分组连接行失败,分组ID: %s,错误: %v", groupID, err)
 		}
 
-		// 转换布尔值
-		conn.UseSSHTunnel = useSSHTunnel != 0
-		conn.AutoConnect = autoConnect != 0
+		var conn types.ConnectionWithDetails
+		conn.Connection.ID = id
+		conn.Connection.GroupID = gid
+		conn.Connection.Name = name
+		conn.Connection.Description = description
+		conn.Connection.Kind = kind
+		conn.Connection.Color = color
+		conn.Connection.AutoConnect = autoConnectInt != 0
+		conn.Connection.DisplayOrder = displayOrder
+		if createdAtNull.Valid {
+			if t, err := time.Parse(time.RFC3339Nano, createdAtNull.String); err == nil {
+				conn.Connection.CreatedAt = t
+			} else if t2, err2 := time.Parse(time.RFC3339, createdAtNull.String); err2 == nil {
+				conn.Connection.CreatedAt = t2
+			} else if t3, err3 := time.Parse("2006-01-02 15:04:05", createdAtNull.String); err3 == nil {
+				conn.Connection.CreatedAt = t3
+			}
+		}
+		if updatedAtNull.Valid {
+			if t, err := time.Parse(time.RFC3339Nano, updatedAtNull.String); err == nil {
+				conn.Connection.UpdatedAt = t
+			} else if t2, err2 := time.Parse(time.RFC3339, updatedAtNull.String); err2 == nil {
+				conn.Connection.UpdatedAt = t2
+			} else if t3, err3 := time.Parse("2006-01-02 15:04:05", updatedAtNull.String); err3 == nil {
+				conn.Connection.UpdatedAt = t3
+			}
+		}
+
+		// try load db detail
+		var detail types.DBConnectionDetail
+		var lastConnectedNull sql.NullString
+		var useSSHTunnelIntLocal int
+		var passwordNull sql.NullString
+		var connStrNull sql.NullString
+		var sshTunnelConnNull sql.NullString
+		err = sm.db.QueryRow(`SELECT type, version, server, port, username, password, database_name, connection_string, use_ssh_tunnel, ssh_tunnel_connection_id, last_connected FROM db_connections WHERE connection_id = ?`, id).Scan(&detail.Type, &detail.Version, &detail.Server, &detail.Port, &detail.Username, &passwordNull, &detail.DatabaseName, &connStrNull, &useSSHTunnelIntLocal, &sshTunnelConnNull, &lastConnectedNull)
+		if err == nil {
+			if passwordNull.Valid {
+				detail.Password = passwordNull.String
+			} else {
+				detail.Password = ""
+			}
+			if connStrNull.Valid {
+				detail.ConnectionString = connStrNull.String
+			} else {
+				detail.ConnectionString = ""
+			}
+			if sshTunnelConnNull.Valid {
+				detail.SSHTunnelConnection = sshTunnelConnNull.String
+			} else {
+				detail.SSHTunnelConnection = ""
+			}
+			detail.ConnectionID = id
+			detail.UseSSHTunnel = useSSHTunnelIntLocal != 0
+			if lastConnectedNull.Valid {
+				if t, err := time.Parse(time.RFC3339Nano, lastConnectedNull.String); err == nil {
+					detail.LastConnected = t
+				} else if t2, err2 := time.Parse(time.RFC3339, lastConnectedNull.String); err2 == nil {
+					detail.LastConnected = t2
+				} else if t3, err3 := time.Parse("2006-01-02 15:04:05", lastConnectedNull.String); err3 == nil {
+					detail.LastConnected = t3
+				}
+			}
+			conn.DBDetail = &detail
+		} else {
+			// try server detail
+			var sdetail types.ServerConnectionDetail
+			var useSudoIntLocal int
+			var authTypeNull sql.NullString
+			var privateKeyNull sql.NullString
+			err2 := sm.db.QueryRow(`SELECT type, version, server, port, username, auth_type, private_key, use_sudo FROM server_connections WHERE connection_id = ?`, id).Scan(&sdetail.Type, &sdetail.Version, &sdetail.Server, &sdetail.Port, &sdetail.Username, &authTypeNull, &privateKeyNull, &useSudoIntLocal)
+			if err2 == nil {
+				if authTypeNull.Valid {
+					sdetail.AuthType = authTypeNull.String
+				} else {
+					sdetail.AuthType = ""
+				}
+				if privateKeyNull.Valid {
+					sdetail.PrivateKey = privateKeyNull.String
+				} else {
+					sdetail.PrivateKey = ""
+				}
+				sdetail.ConnectionID = id
+				sdetail.UseSudo = useSudoIntLocal != 0
+				conn.ServerDetail = &sdetail
+			}
+		}
 
-		// 转换时间
-		conn.LastConnected, _ = time.Parse("2006-01-02 15:04:05", string(lastConnected))
-		conn.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", string(createdAt))
-		conn.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", string(updatedAt))
+		// load scripts (ignore error)
+		scripts, _ := sm.ListScripts(id)
+		conn.Scripts = scripts
 
 		connections = append(connections, conn)
 	}
@@ -253,13 +360,13 @@ func (sm *StorageManager) UpdateConnectionGroup(groupID, name, description, icon
 
 	now := time.Now()
 
-	sql := `
+	query := `
 		UPDATE connection_groups
 		SET name = ?, description = ?, icon = ?,
 			display_order = ?, updated_at = ?
 		WHERE id = ?`
 
-	result, err := sm.db.Exec(sql,
+	result, err := sm.db.Exec(query,
 		name, description, icon,
 		displayOrder, now, groupID,
 	)
@@ -302,12 +409,12 @@ func (sm *StorageManager) MoveConnectionGroup(groupID, newParentID string) error
 		return fmt.Errorf("无法将分组移动到其子分组下,分组ID: %s, 子分组ID: %s", groupID, newParentID)
 	}
 
-	sql := `
+	query := `
 		UPDATE connection_groups
 		SET parent_id = ?, updated_at = ?
 		WHERE id = ?`
 
-	result, err := sm.db.Exec(sql, newParentID, now, groupID)
+	result, err := sm.db.Exec(query, newParentID, now, groupID)
 	if err != nil {
 		return fmt.Errorf("移动连接分组失败,分组ID: %s, 新父分组ID: %s, 错误: %v", groupID, newParentID, err)
 	}
@@ -410,14 +517,14 @@ func (sm *StorageManager) DeleteConnectionGroup(groupID string, cascadeDeleteChi
 // GetRootConnectionGroup 获取根连接分组
 func (sm *StorageManager) GetRootConnectionGroup(loadConnections bool, recursive bool) (*ConnectionGroup, error) {
 	// 查找 parent_id 为 NULL 的分组(根分组)
-	sql := `
+	query := `
 		SELECT id 
 		FROM connection_groups 
 		WHERE parent_id IS NULL 
 		LIMIT 1`
 
 	var rootID string
-	err := sm.db.QueryRow(sql).Scan(&rootID)
+	err := sm.db.QueryRow(query).Scan(&rootID)
 
 	if err != nil {
 		return nil, fmt.Errorf("查询根连接分组失败,错误: %v", err)
@@ -433,9 +540,9 @@ func (sm *StorageManager) GetConnectionGroupTree(loadConnections bool) (*Connect
 
 // getConnectionGroup 内部无锁版本 - 仅供内部更新方法使用
 func (sm *StorageManager) getConnectionGroup(groupID string) (*ConnectionGroup, error) {
-	sql := `SELECT id, parent_id, name, description, display_order, icon, created_at, updated_at FROM connection_groups WHERE id = ?`
+	query := `SELECT id, parent_id, name, description, display_order, icon, created_at, updated_at FROM connection_groups WHERE id = ?`
 
-	row := sm.db.QueryRow(sql, groupID)
+	row := sm.db.QueryRow(query, groupID)
 	group := &ConnectionGroup{}
 
 	var createdAt, updatedAt []byte
@@ -458,72 +565,12 @@ func (sm *StorageManager) getConnectionGroup(groupID string) (*ConnectionGroup,
 
 	// 初始化子分组和连接切片
 	group.Subgroups = []ConnectionGroup{}
-	group.Connections = []DBConnection{}
+	group.Connections = []types.ConnectionWithDetails{}
 
 	return group, nil
 }
 
-// deleteChildGroups 递归删除子分组
-func (sm *StorageManager) deleteChildGroups(parentID string) error {
-	// 查询所有直接子分组
-	rows, err := sm.db.Query("SELECT id FROM connection_groups WHERE parent_id = ?", parentID)
-	if err != nil {
-		return fmt.Errorf("查询子分组失败,父分组ID: %s,错误: %v", parentID, err)
-	}
-	defer rows.Close()
-
-	for rows.Next() {
-		var childID string
-		err := rows.Scan(&childID)
-		if err != nil {
-			return fmt.Errorf("扫描子分组ID失败,错误: %v", err)
-		}
-
-		// 递归删除子分组的子分组
-		err = sm.deleteChildGroups(childID)
-		if err != nil {
-			return fmt.Errorf("递归删除子分组失败,子分组ID: %s,错误: %v", childID, err)
-		}
-
-		// 删除子分组下的所有连接
-		err = sm.deleteGroupConnections(childID)
-		if err != nil {
-			return fmt.Errorf("删除子分组连接失败,子分组ID: %s,错误: %v", childID, err)
-		}
-
-		// 删除子分组
-		_, err = sm.db.Exec("DELETE FROM connection_groups WHERE id = ?", childID)
-		if err != nil {
-			return fmt.Errorf("删除子分组失败,子分组ID: %s,错误: %v", childID, err)
-		}
-	}
-
-	if err = rows.Err(); err != nil {
-		return fmt.Errorf("遍历子分组结果集失败,父分组ID: %s,错误: %v", parentID, err)
-	}
-
-	return nil
-}
-
-// deleteGroupConnections 删除分组下的所有连接
-func (sm *StorageManager) deleteGroupConnections(groupID string) error {
-	result, err := sm.db.Exec("DELETE FROM connections WHERE group_id = ?", groupID)
-	if err != nil {
-		return fmt.Errorf("删除分组连接失败,分组ID: %s,错误: %v", groupID, err)
-	}
-
-	rowsAffected, err := result.RowsAffected()
-	if err != nil {
-		return fmt.Errorf("获取删除连接影响行数失败,分组ID: %s,错误: %v", groupID, err)
-	}
-
-	// 记录删除的连接数量(可选,用于日志)
-	if rowsAffected > 0 {
-		// 可以在这里添加日志记录
-	}
-
-	return nil
-}
+// (已移除非事务版本的递归删除与删除连接函数,使用事务版本以保证一致性)
 
 // deleteChildGroupsInTransaction 在事务中递归删除子分组
 func (sm *StorageManager) deleteChildGroupsInTransaction(tx *sql.Tx, parentID string) error {

+ 156 - 140
service/internal/common/manager/storage/db_storage/connection_operations.go

@@ -9,7 +9,7 @@ import (
 )
 
 // UpdateConnection 更新连接信息
-func (sm *StorageManager) UpdateConnection(connID string, req *types.UpdateConnectionRequest) (*DBConnection, error) {
+func (sm *StorageManager) UpdateConnection(connID string, req *types.UpdateConnectionRequest) (*types.ConnectionWithDetails, error) {
 	sm.mu.Lock()
 	defer sm.mu.Unlock()
 
@@ -31,46 +31,16 @@ func (sm *StorageManager) UpdateConnection(connID string, req *types.UpdateConne
 		setParts = append(setParts, "description = ?")
 		args = append(args, *req.Description)
 	}
-	if req.DBType != nil {
-		setParts = append(setParts, "db_type = ?")
-		args = append(args, *req.DBType)
-	}
-	if req.DBVersion != nil {
-		setParts = append(setParts, "db_version = ?")
-		args = append(args, *req.DBVersion)
-	}
-	if req.Server != nil {
-		setParts = append(setParts, "server = ?")
-		args = append(args, *req.Server)
-	}
-	if req.Port != nil {
-		setParts = append(setParts, "port = ?")
-		args = append(args, *req.Port)
-	}
-	if req.Username != nil {
-		setParts = append(setParts, "username = ?")
-		args = append(args, *req.Username)
-	}
-	if req.Password != nil {
-		setParts = append(setParts, "password_encrypted = ?")
-		args = append(args, *req.Password)
-	}
-	if req.Database != nil {
-		setParts = append(setParts, "database = ?")
-		args = append(args, *req.Database)
-	}
-	if req.ConnectionString != nil {
-		setParts = append(setParts, "connection_string = ?")
-		args = append(args, *req.ConnectionString)
-	}
-	if req.UseSSHTunnel != nil {
-		useSSHTunnelInt := 0
-		if *req.UseSSHTunnel {
-			useSSHTunnelInt = 1
-		}
-		setParts = append(setParts, "use_ssh_tunnel = ?")
-		args = append(args, useSSHTunnelInt)
+	// 支持新的 Type/Version/Kind 字段,同时兼容旧字段 DBType/DBVersion
+	if req.Kind != nil {
+		setParts = append(setParts, "kind = ?")
+		args = append(args, *req.Kind)
 	}
+
+	// Note: detail fields (type/version/server/port/username/password/database/connection_string/use_ssh_tunnel)
+	// moved to specialized detail tables (db_connections / server_connections).
+	// We do not update them in the base `connections` table; instead we perform conditional upserts
+	// on the corresponding detail tables after updating base fields.
 	if req.Color != nil {
 		setParts = append(setParts, "color = ?")
 		args = append(args, *req.Color)
@@ -111,8 +81,148 @@ func (sm *StorageManager) UpdateConnection(connID string, req *types.UpdateConne
 		return nil, fmt.Errorf("未找到要更新的数据库连接,连接ID: %s", connID)
 	}
 
-	// 使用内部无锁函数获取更新后的连接信息
-	return sm.getConnection(connID)
+	// 如果有 detail 字段需要更新,则在事务中对 detail 表执行 upsert
+	// 构建 db detail upsert
+	var dbUpsertNeeded bool
+	if req.Type != nil || req.Version != nil || req.Server != nil || req.Port != nil || req.Username != nil || req.Password != nil || req.Database != nil || req.ConnectionString != nil || req.UseSSHTunnel != nil {
+		dbUpsertNeeded = true
+		// fill with possible nil/defaults; we'll set actual values below
+	}
+
+	var serverUpsertNeeded bool
+	if req.AuthType != nil || req.PrivateKey != nil || req.UseSudo != nil {
+		serverUpsertNeeded = true
+	}
+
+	if dbUpsertNeeded || serverUpsertNeeded {
+		tx, err := sm.db.Begin()
+		if err != nil {
+			return nil, fmt.Errorf("开始事务失败,连接ID: %s,错误: %v", connID, err)
+		}
+
+		// Handle db_connections upsert
+		if dbUpsertNeeded {
+			// Read existing detail to preserve unchanged fields
+			var existing types.DBConnectionDetail
+			err := tx.QueryRow(`SELECT type, version, server, port, username, password, database_name, connection_string, use_ssh_tunnel, ssh_tunnel_connection_id, last_connected FROM db_connections WHERE connection_id = ?`, connID).Scan(&existing.Type, &existing.Version, &existing.Server, &existing.Port, &existing.Username, &existing.Password, &existing.DatabaseName, &existing.ConnectionString, &existing.UseSSHTunnel, &existing.SSHTunnelConnection, &existing.LastConnected)
+			if err != nil && err.Error() != "sql: no rows in result set" {
+				tx.Rollback()
+				return nil, fmt.Errorf("读取 db_connections 失败,连接ID: %s,错误: %v", connID, err)
+			}
+
+			// decide values (prefer explicit Type/Version over DBType/DBVersion)
+			typ := existing.Type
+			if req.Type != nil {
+				typ = *req.Type
+			}
+			ver := existing.Version
+			if req.Version != nil {
+				ver = *req.Version
+			}
+			server := existing.Server
+			if req.Server != nil {
+				server = *req.Server
+			}
+			port := existing.Port
+			if req.Port != nil {
+				port = *req.Port
+			}
+			username := existing.Username
+			if req.Username != nil {
+				username = *req.Username
+			}
+			password := existing.Password
+			if req.Password != nil {
+				password = *req.Password
+			}
+			dbname := existing.DatabaseName
+			if req.Database != nil {
+				dbname = *req.Database
+			}
+			connStr := existing.ConnectionString
+			if req.ConnectionString != nil {
+				connStr = *req.ConnectionString
+			}
+			useSSHTunnel := existing.UseSSHTunnel
+			if req.UseSSHTunnel != nil {
+				useSSHTunnel = *req.UseSSHTunnel
+			}
+
+			// Use INSERT OR REPLACE to upsert the detail row
+			useSSHTunnelInt := 0
+			if useSSHTunnel {
+				useSSHTunnelInt = 1
+			}
+			_, err = tx.Exec(`INSERT OR REPLACE INTO db_connections (connection_id, type, version, server, port, username, password, database_name, connection_string, use_ssh_tunnel, ssh_tunnel_connection_id, last_connected) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+				connID, typ, ver, server, port, username, password, dbname, connStr, useSSHTunnelInt, existing.SSHTunnelConnection, existing.LastConnected)
+			if err != nil {
+				tx.Rollback()
+				return nil, fmt.Errorf("upsert db_connections 失败,连接ID: %s,错误: %v", connID, err)
+			}
+		}
+
+		// Handle server_connections upsert
+		if serverUpsertNeeded {
+			var existing types.ServerConnectionDetail
+			err := tx.QueryRow(`SELECT type, version, server, port, username, auth_type, private_key, use_sudo FROM server_connections WHERE connection_id = ?`, connID).Scan(&existing.Type, &existing.Version, &existing.Server, &existing.Port, &existing.Username, &existing.AuthType, &existing.PrivateKey, &existing.UseSudo)
+			if err != nil && err.Error() != "sql: no rows in result set" {
+				tx.Rollback()
+				return nil, fmt.Errorf("读取 server_connections 失败,连接ID: %s,错误: %v", connID, err)
+			}
+
+			typ := existing.Type
+			if req.Type != nil {
+				typ = *req.Type
+			}
+			ver := existing.Version
+			if req.Version != nil {
+				ver = *req.Version
+			}
+			server := existing.Server
+			if req.Server != nil {
+				server = *req.Server
+			}
+			port := existing.Port
+			if req.Port != nil {
+				port = *req.Port
+			}
+			username := existing.Username
+			if req.Username != nil {
+				username = *req.Username
+			}
+			authType := existing.AuthType
+			if req.AuthType != nil {
+				authType = *req.AuthType
+			}
+			privateKey := existing.PrivateKey
+			if req.PrivateKey != nil {
+				privateKey = *req.PrivateKey
+			}
+			useSudo := existing.UseSudo
+			if req.UseSudo != nil {
+				useSudo = *req.UseSudo
+			}
+
+			useSudoInt := 0
+			if useSudo {
+				useSudoInt = 1
+			}
+			_, err = tx.Exec(`INSERT OR REPLACE INTO server_connections (connection_id, type, version, server, port, username, auth_type, private_key, use_sudo) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+				connID, typ, ver, server, port, username, authType, privateKey, useSudoInt)
+			if err != nil {
+				tx.Rollback()
+				return nil, fmt.Errorf("upsert server_connections 失败,连接ID: %s,错误: %v", connID, err)
+			}
+		}
+
+		if err := tx.Commit(); err != nil {
+			tx.Rollback()
+			return nil, fmt.Errorf("提交 detail 表更新事务失败,连接ID: %s,错误: %v", connID, err)
+		}
+	}
+
+	// 使用已实现的 GetConnection 获取更新后的连接信息
+	return sm.GetConnection(connID)
 }
 
 // DeleteConnection 删除连接
@@ -141,7 +251,7 @@ func (sm *StorageManager) DeleteConnection(connID string) error {
 }
 
 // MoveConnection 移动连接到指定分组
-func (sm *StorageManager) MoveConnection(connID, targetGroupID string) (*DBConnection, error) {
+func (sm *StorageManager) MoveConnection(connID, targetGroupID string) (*types.ConnectionWithDetails, error) {
 	sm.mu.Lock()
 	defer sm.mu.Unlock()
 
@@ -163,64 +273,12 @@ func (sm *StorageManager) MoveConnection(connID, targetGroupID string) (*DBConne
 		return nil, fmt.Errorf("未找到要移动的数据库连接,连接ID: %s", connID)
 	}
 
-	// 使用内部无锁函数获取更新后的连接信息
-	return sm.getConnection(connID)
+	return sm.GetConnection(connID)
 }
 
 // GetAllConnections 获取所有连接
-func (sm *StorageManager) GetAllConnections() ([]DBConnection, error) {
-	sm.mu.RLock()
-	defer sm.mu.RUnlock()
-
-	sql := `SELECT 
-		id, group_id, name, description, db_type, db_version, 
-		server, port, database, username, password_encrypted, 
-		use_ssh_tunnel, connection_string, color, last_connected, 
-		auto_connect, display_order, created_at, updated_at
-	FROM connections
-	ORDER BY name`
-
-	rows, err := sm.db.Query(sql)
-	if err != nil {
-		return nil, fmt.Errorf("查询所有数据库连接失败,错误: %v", err)
-	}
-	defer rows.Close()
-
-	var connections []DBConnection
-	for rows.Next() {
-		var conn DBConnection
-		var useSSHTunnel, autoConnect int
-		var lastConnected, createdAt, updatedAt []byte
-
-		err := rows.Scan(
-			&conn.ID, &conn.GroupID, &conn.Name, &conn.Description,
-			&conn.DBType, &conn.DBVersion, &conn.Server, &conn.Port,
-			&conn.Database, &conn.Username, &conn.Password,
-			&useSSHTunnel, &conn.ConnectionString, &conn.Color, &lastConnected,
-			&autoConnect, &conn.DisplayOrder, &createdAt, &updatedAt,
-		)
-
-		if err != nil {
-			return nil, fmt.Errorf("扫描数据库连接行失败,错误: %v", err)
-		}
-
-		// 转换布尔值
-		conn.UseSSHTunnel = useSSHTunnel != 0
-		conn.AutoConnect = autoConnect != 0
-
-		// 转换时间
-		conn.LastConnected, _ = time.Parse("2006-01-02 15:04:05", string(lastConnected))
-		conn.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", string(createdAt))
-		conn.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", string(updatedAt))
-
-		connections = append(connections, conn)
-	}
-
-	if err = rows.Err(); err != nil {
-		return nil, fmt.Errorf("遍历数据库连接结果集失败,错误: %v", err)
-	}
-
-	return connections, nil
+func (sm *StorageManager) GetAllConnections() ([]types.ConnectionWithDetails, error) {
+	return sm.ListConnections()
 }
 
 // CreateDefaultConfig 创建默认配置
@@ -257,45 +315,3 @@ func (sm *StorageManager) CreateDefaultConfig() error {
 
 	return nil
 }
-
-// getConnection 内部无锁版本 - 仅供内部更新方法使用
-func (sm *StorageManager) getConnection(connID string) (*DBConnection, error) {
-	sql := `SELECT 
-id, group_id, name, description, db_type, db_version, 
-server, port, database, username, password_encrypted, 
-use_ssh_tunnel, connection_string, color, last_connected, 
-auto_connect, display_order, created_at, updated_at
-FROM connections WHERE id = ?`
-
-	row := sm.db.QueryRow(sql, connID)
-	conn := &DBConnection{}
-
-	var lastConnected, createdAt, updatedAt []byte
-	var useSSHTunnel, autoConnect int // SQLite以int形式存储布尔值
-
-	err := row.Scan(
-		&conn.ID, &conn.GroupID, &conn.Name, &conn.Description,
-		&conn.DBType, &conn.DBVersion, &conn.Server, &conn.Port,
-		&conn.Database, &conn.Username, &conn.Password,
-		&useSSHTunnel, &conn.ConnectionString, &conn.Color, &lastConnected,
-		&autoConnect, &conn.DisplayOrder, &createdAt, &updatedAt,
-	)
-
-	if err != nil {
-		if err.Error() == "sql: no rows in result set" {
-			return nil, fmt.Errorf("未找到数据库连接,连接ID: %s", connID)
-		}
-		return nil, fmt.Errorf("查询数据库连接失败,连接ID: %s,错误: %v", connID, err)
-	}
-
-	// 转换布尔值
-	conn.UseSSHTunnel = useSSHTunnel == 1
-	conn.AutoConnect = autoConnect == 1
-
-	// 解析时间
-	conn.LastConnected, _ = time.Parse("2006-01-02 15:04:05", string(lastConnected))
-	conn.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", string(createdAt))
-	conn.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", string(updatedAt))
-
-	return conn, nil
-}

+ 369 - 172
service/internal/common/manager/storage/db_storage/manager.go

@@ -21,12 +21,11 @@ import (
 // ========== 数据结构 ==========
 
 // 使用 types 包中的共享类型
-type DBConnection = types.DBConnection
 type ConnectionGroup = types.ConnectionGroup
-type SQLScript = types.SQLScript
+type Script = types.Script
 type ScriptGroup = types.ScriptGroup
 
-// StorageManager SQLite存储管理器
+// SQLite 存储管理器
 type StorageManager struct {
 	dbPath string
 	db     *sql.DB
@@ -72,7 +71,7 @@ func NewStorageManager(dbPath string) (*StorageManager, error) {
 	return sm, nil
 }
 
-// initDatabase 初始化数据库表
+// 初始化数据库表
 func (sm *StorageManager) initDatabase() error {
 	// 创建连接分组表
 	connectionGroupSQL := `
@@ -88,30 +87,52 @@ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 FOREIGN KEY (parent_id) REFERENCES connection_groups(id) ON DELETE CASCADE
 );`
 
-	// 创建连接表
+	// 创建连接表(基础通用信息,不包含 type/version)
 	connectionSQL := `
-CREATE TABLE IF NOT EXISTS connections (
-id TEXT PRIMARY KEY,
-group_id TEXT,
-name TEXT NOT NULL,
-description TEXT,
-db_type TEXT,
-db_version TEXT,
-server TEXT,
-port INTEGER,
-database TEXT,
-username TEXT,
-password_encrypted TEXT,
-use_ssh_tunnel BOOLEAN DEFAULT 0,
-connection_string TEXT,
-color TEXT,
-last_connected TIMESTAMP,
-auto_connect BOOLEAN DEFAULT 0,
-display_order INTEGER DEFAULT 0,
-created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-FOREIGN KEY (group_id) REFERENCES connection_groups(id) ON DELETE SET NULL
-);`
+		CREATE TABLE IF NOT EXISTS connections (
+		id TEXT PRIMARY KEY,
+		group_id TEXT,
+		name TEXT NOT NULL,
+		description TEXT,
+		kind TEXT,
+		color TEXT,
+		auto_connect INTEGER DEFAULT 0,
+		display_order INTEGER DEFAULT 0,
+		created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+		updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+		FOREIGN KEY (group_id) REFERENCES connection_groups(id) ON DELETE SET NULL
+		);`
+
+	// 专用表:db_connections
+	dbConnSQL := `
+		CREATE TABLE IF NOT EXISTS db_connections (
+			connection_id TEXT PRIMARY KEY REFERENCES connections(id) ON DELETE CASCADE,
+			type TEXT NOT NULL,
+			version TEXT,
+			server TEXT,
+			port INTEGER,
+			username TEXT,
+			password TEXT,
+			database_name TEXT,
+			connection_string TEXT,
+			use_ssh_tunnel INTEGER DEFAULT 0,
+			ssh_tunnel_connection_id TEXT,
+			last_connected TIMESTAMP
+		);`
+
+	// 专用表:server_connections
+	serverConnSQL := `
+		CREATE TABLE IF NOT EXISTS server_connections (
+			connection_id TEXT PRIMARY KEY REFERENCES connections(id) ON DELETE CASCADE,
+			type TEXT NOT NULL,
+			version TEXT,
+			server TEXT,
+			port INTEGER,
+			username TEXT,
+			auth_type TEXT,
+			private_key TEXT,
+			use_sudo INTEGER DEFAULT 0
+		);`
 
 	// 创建脚本分组表
 	scriptGroupSQL := `
@@ -125,9 +146,9 @@ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 FOREIGN KEY (parent_id) REFERENCES script_groups(id) ON DELETE CASCADE
 );`
 
-	// 创建SQL脚本表
-	sqlScriptSQL := `
-CREATE TABLE IF NOT EXISTS sql_scripts (
+	// 创建脚本表(通用,scripts)
+	scriptsSQL := `
+CREATE TABLE IF NOT EXISTS scripts (
 id TEXT PRIMARY KEY,
 connection_id TEXT,
 group_id TEXT,
@@ -135,7 +156,14 @@ name TEXT NOT NULL,
 description TEXT,
 content TEXT,
 favorite BOOLEAN DEFAULT 0,
+language TEXT,
+metadata TEXT,
+tags TEXT,
+enabled INTEGER DEFAULT 1,
+owner TEXT,
+checksum TEXT,
 last_executed TIMESTAMP,
+last_run_status TEXT,
 execution_count INTEGER DEFAULT 0,
 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
@@ -150,7 +178,7 @@ key TEXT PRIMARY KEY,
 value TEXT
 );`
 
-	tables := []string{connectionGroupSQL, connectionSQL, scriptGroupSQL, sqlScriptSQL, configSQL}
+	tables := []string{connectionGroupSQL, connectionSQL, dbConnSQL, serverConnSQL, scriptGroupSQL, scriptsSQL, configSQL}
 
 	for i, sql := range tables {
 		if _, err := sm.db.Exec(sql); err != nil {
@@ -159,13 +187,17 @@ value TEXT
 	}
 
 	// 检查根连接分组是否已存在
-	var count int
-	err := sm.db.QueryRow("SELECT COUNT(*) FROM connection_groups WHERE id = ?", "root_default").Scan(&count)
+	var countNull sql.NullInt64
+	err := sm.db.QueryRow("SELECT COUNT(*) FROM connection_groups WHERE id = ?", "root_default").Scan(&countNull)
 	if err != nil {
 		return fmt.Errorf("检查根连接分组失败,错误: %v", err)
 	}
 
 	// 只有在根分组不存在时才创建
+	count := int64(0)
+	if countNull.Valid {
+		count = countNull.Int64
+	}
 	if count == 0 {
 		// 插入根连接分组
 		rootGroupSQL := `
@@ -187,17 +219,17 @@ VALUES (?, ?, ?, ?, ?)`
 	return nil
 }
 
-// Close 关闭数据库连接
+// 关闭数据库连接
 func (sm *StorageManager) Close() error {
 	return sm.db.Close()
 }
 
-// generateID 生成唯一ID
+// 生成唯一 ID
 func generateID() string {
 	return uuid.New().String()[:8]
 }
 
-// generateGroupID 根据组名和随机值生成ID
+// 根据组名和随机值生成 ID
 func generateGroupID(name string) string {
 	// 检查是否包含非拉丁字符(如中文字符)
 	hasNonLatin := false
@@ -261,7 +293,7 @@ func generateGroupID(name string) string {
 	return fmt.Sprintf("%s_%s", prefix, randomPart)
 }
 
-// generateScriptID 根据分组ID和脚本名生成关联性ID
+// 根据分组 ID 和脚本名生成关联性 ID
 func generateScriptID(groupID, name string) string {
 	// 从分组ID中提取前缀(分组ID格式为 prefix_random)
 	var groupPrefix string
@@ -331,19 +363,7 @@ func generateScriptID(groupID, name string) string {
 	return fmt.Sprintf("%s_%s_%s", groupPrefix, scriptPrefix, randomPart)
 }
 
-// marshalTags 将标签数组序列化为JSON字符串
-func marshalTags(tags []string) (string, error) {
-	if tags == nil {
-		return "[]", nil
-	}
-	data, err := json.Marshal(tags)
-	if err != nil {
-		return "", err
-	}
-	return string(data), nil
-}
-
-// unmarshalTags 将JSON字符串反序列化为标签数组
+// 将 JSON 字符串反序列化为标签数组
 func unmarshalTags(tagsStr string) ([]string, error) {
 	if tagsStr == "" {
 		return []string{}, nil
@@ -353,196 +373,342 @@ func unmarshalTags(tagsStr string) ([]string, error) {
 	return tags, err
 }
 
-// CreateConnection 创建数据库连接
-func (sm *StorageManager) CreateConnection(groupID, name, description, dbType, dbVersion, server string, port int, username, password, database, connectionString string, useSSHTunnel bool, color string, autoConnect bool, displayOrder int) (*DBConnection, error) {
+// 创建数据库或服务器连接,返回聚合的连接详情
+func (sm *StorageManager) CreateConnection(groupID, name, description, kind, typ, version, server string, port int, username, password, database, connectionString string, useSSHTunnel bool, color string, autoConnect bool, displayOrder int) (*types.ConnectionWithDetails, error) {
 	sm.mu.Lock()
 	defer sm.mu.Unlock()
 
 	id := generateID()
 	now := time.Now()
 
-	conn := &DBConnection{
-		ID:               id,
-		GroupID:          groupID,
-		Name:             name,
-		Description:      description,
-		DBType:           dbType,
-		DBVersion:        dbVersion,
-		Server:           server,
-		Port:             port,
-		Username:         username,
-		Password:         password,
-		Database:         database,
-		ConnectionString: connectionString,
-		UseSSHTunnel:     useSSHTunnel,
-		Color:            color,
-		LastConnected:    now,
-		AutoConnect:      autoConnect,
-		DisplayOrder:     displayOrder,
-		CreatedAt:        now,
-		UpdatedAt:        now,
-	}
-
-	sql := `
-INSERT INTO connections (
-id, group_id, name, description, db_type, db_version, 
-server, port, database, username, password_encrypted, 
-use_ssh_tunnel, connection_string, color, last_connected, 
-auto_connect, display_order, created_at, updated_at
-) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
-
-	useSSHTunnelInt := 0
-	if useSSHTunnel {
-		useSSHTunnelInt = 1
+	tx, err := sm.db.Begin()
+	if err != nil {
+		return nil, fmt.Errorf("开始事务失败: %v", err)
 	}
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		}
+	}()
 
+	insertConnSQL := `INSERT INTO connections (id, group_id, name, description, kind, color, auto_connect, display_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
 	autoConnectInt := 0
 	if autoConnect {
 		autoConnectInt = 1
 	}
 
-	_, execErr := sm.db.Exec(sql,
-		id, groupID, name, description, dbType, dbVersion,
-		server, port, database, username, password,
-		useSSHTunnelInt, connectionString, color, now,
-		autoConnectInt, displayOrder, now, now)
+	_, err = tx.Exec(insertConnSQL, id, groupID, name, description, kind, color, autoConnectInt, displayOrder, now, now)
+	if err != nil {
+		return nil, fmt.Errorf("插入 connections 失败: %v", err)
+	}
+
+	var connDetail *types.DBConnectionDetail
+	var serverDetail *types.ServerConnectionDetail
 
-	if execErr != nil {
-		return nil, fmt.Errorf("创建数据库连接失败,连接名称: %s,数据库类型: %s,服务器: %s:%d,错误: %v", name, dbType, server, port, execErr)
+	if kind == "database" {
+		insertDBSQL := `INSERT INTO db_connections (connection_id, type, version, server, port, username, password, database_name, connection_string, use_ssh_tunnel, ssh_tunnel_connection_id, last_connected) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
+		useSSHTunnelInt := 0
+		if useSSHTunnel {
+			useSSHTunnelInt = 1
+		}
+		_, err = tx.Exec(insertDBSQL, id, typ, version, server, port, username, password, database, connectionString, useSSHTunnelInt, nil, now)
+		if err != nil {
+			return nil, fmt.Errorf("插入 db_connections 失败: %v", err)
+		}
+		connDetail = &types.DBConnectionDetail{
+			ConnectionID:        id,
+			Type:                typ,
+			Version:             version,
+			Server:              server,
+			Port:                port,
+			Username:            username,
+			Password:            password,
+			DatabaseName:        database,
+			ConnectionString:    connectionString,
+			UseSSHTunnel:        useSSHTunnel,
+			SSHTunnelConnection: "",
+			LastConnected:       now,
+		}
+	} else if kind == "server" {
+		insertServerSQL := `INSERT INTO server_connections (connection_id, type, version, server, port, username, auth_type, private_key, use_sudo) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
+		_, err = tx.Exec(insertServerSQL, id, typ, version, server, port, username, "", "", 0)
+		if err != nil {
+			return nil, fmt.Errorf("插入 server_connections 失败: %v", err)
+		}
+		serverDetail = &types.ServerConnectionDetail{
+			ConnectionID: id,
+			Type:         typ,
+			Version:      version,
+			Server:       server,
+			Port:         port,
+			Username:     username,
+			AuthType:     "",
+			PrivateKey:   "",
+			UseSudo:      false,
+		}
+	}
+
+	if err = tx.Commit(); err != nil {
+		return nil, fmt.Errorf("提交事务失败: %v", err)
+	}
+
+	// 构建返回对象
+	result := &types.ConnectionWithDetails{
+		Connection: types.Connection{
+			ID:           id,
+			GroupID:      groupID,
+			Name:         name,
+			Description:  description,
+			Kind:         kind,
+			Color:        color,
+			AutoConnect:  autoConnect,
+			DisplayOrder: displayOrder,
+			CreatedAt:    now,
+			UpdatedAt:    now,
+		},
+		DBDetail:     connDetail,
+		ServerDetail: serverDetail,
+		Scripts:      []types.Script{},
 	}
 
-	return conn, nil
+	return result, nil
 }
 
-// GetConnection 获取连接信息
-func (sm *StorageManager) GetConnection(connID string) (*DBConnection, error) {
+// 获取连接信息并聚合详情
+func (sm *StorageManager) GetConnection(connID string) (*types.ConnectionWithDetails, error) {
 	sm.mu.RLock()
 	defer sm.mu.RUnlock()
 
-	sql := `SELECT 
-id, group_id, name, description, db_type, db_version, 
-server, port, database, username, password_encrypted, 
-use_ssh_tunnel, connection_string, color, last_connected, 
-auto_connect, display_order, created_at, updated_at
-FROM connections WHERE id = ?`
-
-	row := sm.db.QueryRow(sql, connID)
-	conn := &DBConnection{}
-
-	var lastConnected, createdAt, updatedAt []byte
-	var useSSHTunnel, autoConnect int // SQLite以int形式存储布尔值
+	query := `SELECT id, group_id, name, description, kind, color, auto_connect, display_order, created_at, updated_at FROM connections WHERE id = ?`
+	row := sm.db.QueryRow(query, connID)
 
-	err := row.Scan(
-		&conn.ID, &conn.GroupID, &conn.Name, &conn.Description,
-		&conn.DBType, &conn.DBVersion, &conn.Server, &conn.Port,
-		&conn.Database, &conn.Username, &conn.Password,
-		&useSSHTunnel, &conn.ConnectionString, &conn.Color, &lastConnected,
-		&autoConnect, &conn.DisplayOrder, &createdAt, &updatedAt,
-	)
+	var idNull, gidNull, nameNull, descriptionNull, kindNull, colorNull sql.NullString
+	var autoConnectNull, displayOrderNull sql.NullInt64
+	var createdAtNull, updatedAtNull sql.NullString
 
+	err := row.Scan(&idNull, &gidNull, &nameNull, &descriptionNull, &kindNull, &colorNull, &autoConnectNull, &displayOrderNull, &createdAtNull, &updatedAtNull)
 	if err != nil {
-		if err.Error() == "sql: no rows in result set" {
+		if err == sql.ErrNoRows {
 			return nil, fmt.Errorf("未找到数据库连接,连接ID: %s", connID)
 		}
 		return nil, fmt.Errorf("查询数据库连接失败,连接ID: %s,错误: %v", connID, err)
 	}
 
-	// 解析时间
-	conn.LastConnected, _ = time.Parse("2006-01-02 15:04:05", string(lastConnected))
-	conn.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", string(createdAt))
-	conn.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", string(updatedAt))
+	// 将 sql.Null* 转换为常规类型(取不到值则使用零值)
+	id := ""
+	if idNull.Valid {
+		id = idNull.String
+	}
+	gid := ""
+	if gidNull.Valid {
+		gid = gidNull.String
+	}
+	name := ""
+	if nameNull.Valid {
+		name = nameNull.String
+	}
+	description := ""
+	if descriptionNull.Valid {
+		description = descriptionNull.String
+	}
+	kind := ""
+	if kindNull.Valid {
+		kind = kindNull.String
+	}
+	color := ""
+	if colorNull.Valid {
+		color = colorNull.String
+	}
 
-	// 转换布尔值
-	conn.UseSSHTunnel = useSSHTunnel != 0
-	conn.AutoConnect = autoConnect != 0
+	autoConnectInt := 0
+	if autoConnectNull.Valid && autoConnectNull.Int64 != 0 {
+		autoConnectInt = 1
+	}
+	displayOrder := 0
+	if displayOrderNull.Valid {
+		displayOrder = int(displayOrderNull.Int64)
+	}
 
-	return conn, nil
+	var conn types.ConnectionWithDetails
+	conn.Connection = types.Connection{
+		ID:           id,
+		GroupID:      gid,
+		Name:         name,
+		Description:  description,
+		Kind:         kind,
+		Color:        color,
+		AutoConnect:  autoConnectInt != 0,
+		DisplayOrder: displayOrder,
+	}
+	if createdAtNull.Valid {
+		if t, err := time.Parse(time.RFC3339Nano, createdAtNull.String); err == nil {
+			conn.Connection.CreatedAt = t
+		} else if t2, err2 := time.Parse(time.RFC3339, createdAtNull.String); err2 == nil {
+			conn.Connection.CreatedAt = t2
+		} else if t3, err3 := time.Parse("2006-01-02 15:04:05", createdAtNull.String); err3 == nil {
+			conn.Connection.CreatedAt = t3
+		}
+	}
+	if updatedAtNull.Valid {
+		if t, err := time.Parse(time.RFC3339Nano, updatedAtNull.String); err == nil {
+			conn.Connection.UpdatedAt = t
+		} else if t2, err2 := time.Parse(time.RFC3339, updatedAtNull.String); err2 == nil {
+			conn.Connection.UpdatedAt = t2
+		} else if t3, err3 := time.Parse("2006-01-02 15:04:05", updatedAtNull.String); err3 == nil {
+			conn.Connection.UpdatedAt = t3
+		}
+	}
+
+	// Load detail according to explicit kind (no compatibility fallbacks)
+	if kind == "database" {
+		var detail types.DBConnectionDetail
+		var lastConnectedNull sql.NullString
+		var useSSHTunnelInt int
+		// 使用 sql.NullString 来接收可能为 NULL 的字符串列
+		var passwordNull sql.NullString
+		var connStrNull sql.NullString
+		var sshTunnelConnNull sql.NullString
+		err = sm.db.QueryRow(`SELECT type, version, server, port, username, password, database_name, connection_string, use_ssh_tunnel, ssh_tunnel_connection_id, last_connected FROM db_connections WHERE connection_id = ?`, id).Scan(&detail.Type, &detail.Version, &detail.Server, &detail.Port, &detail.Username, &passwordNull, &detail.DatabaseName, &connStrNull, &useSSHTunnelInt, &sshTunnelConnNull, &lastConnectedNull)
+		if err != nil {
+			return nil, fmt.Errorf("查询 db_connections 失败,连接ID: %s,错误: %v", id, err)
+		}
+		// 将 Nullable 字段转换为字符串(空值使用空字符串)
+		if passwordNull.Valid {
+			detail.Password = passwordNull.String
+		} else {
+			detail.Password = ""
+		}
+		if connStrNull.Valid {
+			detail.ConnectionString = connStrNull.String
+		} else {
+			detail.ConnectionString = ""
+		}
+		if sshTunnelConnNull.Valid {
+			detail.SSHTunnelConnection = sshTunnelConnNull.String
+		} else {
+			detail.SSHTunnelConnection = ""
+		}
+		detail.ConnectionID = id
+		detail.UseSSHTunnel = useSSHTunnelInt != 0
+		if lastConnectedNull.Valid {
+			if t, err := time.Parse(time.RFC3339Nano, lastConnectedNull.String); err == nil {
+				detail.LastConnected = t
+			} else if t2, err2 := time.Parse(time.RFC3339, lastConnectedNull.String); err2 == nil {
+				detail.LastConnected = t2
+			} else if t3, err3 := time.Parse("2006-01-02 15:04:05", lastConnectedNull.String); err3 == nil {
+				detail.LastConnected = t3
+			}
+		}
+		conn.DBDetail = &detail
+	} else if kind == "server" {
+		var sdetail types.ServerConnectionDetail
+		var useSudoInt int
+		// 使用 sql.NullString 来接收可能为 NULL 的字符串列
+		var authTypeNull sql.NullString
+		var privateKeyNull sql.NullString
+		err = sm.db.QueryRow(`SELECT type, version, server, port, username, auth_type, private_key, use_sudo FROM server_connections WHERE connection_id = ?`, id).Scan(&sdetail.Type, &sdetail.Version, &sdetail.Server, &sdetail.Port, &sdetail.Username, &authTypeNull, &privateKeyNull, &useSudoInt)
+		if err != nil {
+			return nil, fmt.Errorf("查询 server_connections 失败,连接ID: %s,错误: %v", id, err)
+		}
+		if authTypeNull.Valid {
+			sdetail.AuthType = authTypeNull.String
+		} else {
+			sdetail.AuthType = ""
+		}
+		if privateKeyNull.Valid {
+			sdetail.PrivateKey = privateKeyNull.String
+		} else {
+			sdetail.PrivateKey = ""
+		}
+		sdetail.ConnectionID = id
+		sdetail.UseSudo = useSudoInt != 0
+		conn.ServerDetail = &sdetail
+	}
+
+	// load scripts
+	scripts, err := sm.ListScripts(id)
+	if err != nil {
+		return nil, fmt.Errorf("查询脚本失败,连接ID: %s,错误: %v", id, err)
+	}
+	conn.Scripts = scripts
+
+	return &conn, nil
 }
 
-// ListConnections 获取所有连接
-func (sm *StorageManager) ListConnections() ([]*DBConnection, error) {
+// 获取所有连接并聚合详情
+func (sm *StorageManager) ListConnections() ([]types.ConnectionWithDetails, error) {
 	sm.mu.RLock()
 	defer sm.mu.RUnlock()
 
-	sql := `SELECT 
-id, group_id, name, description, db_type, db_version, 
-server, port, database, username, password_encrypted, 
-use_ssh_tunnel, connection_string, color, last_connected, 
-auto_connect, display_order, created_at, updated_at
-FROM connections ORDER BY display_order, name`
-
-	rows, err := sm.db.Query(sql)
+	rows, err := sm.db.Query(`SELECT id FROM connections ORDER BY display_order, name`)
 	if err != nil {
 		return nil, fmt.Errorf("查询数据库连接列表失败,错误: %v", err)
 	}
 	defer rows.Close()
 
-	var connections []*DBConnection
+	var results []types.ConnectionWithDetails
 	for rows.Next() {
-		conn := &DBConnection{}
-
-		var lastConnected, createdAt, updatedAt []byte
-		var useSSHTunnel, autoConnect int
-
-		err := rows.Scan(
-			&conn.ID, &conn.GroupID, &conn.Name, &conn.Description,
-			&conn.DBType, &conn.DBVersion, &conn.Server, &conn.Port,
-			&conn.Database, &conn.Username, &conn.Password,
-			&useSSHTunnel, &conn.ConnectionString, &conn.Color, &lastConnected,
-			&autoConnect, &conn.DisplayOrder, &createdAt, &updatedAt,
-		)
-
+		var idNull sql.NullString
+		if err := rows.Scan(&idNull); err != nil {
+			return nil, fmt.Errorf("扫描连接ID失败: %v", err)
+		}
+		id := ""
+		if idNull.Valid {
+			id = idNull.String
+		}
+		conn, err := sm.GetConnection(id)
 		if err != nil {
-			return nil, fmt.Errorf("解析数据库连接信息失败,错误: %v", err)
+			return nil, err
 		}
+		results = append(results, *conn)
+	}
 
-		// 解析时间
-		conn.LastConnected, _ = time.Parse("2006-01-02 15:04:05", string(lastConnected))
-		conn.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", string(createdAt))
-		conn.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", string(updatedAt))
-
-		// 转换布尔值
-		conn.UseSSHTunnel = useSSHTunnel != 0
-		conn.AutoConnect = autoConnect != 0
-
-		connections = append(connections, conn)
+	if err = rows.Err(); err != nil {
+		return nil, fmt.Errorf("遍历连接结果集失败: %v", err)
 	}
 
-	return connections, nil
+	return results, nil
 }
 
-// ListSQLScripts 获取连接相关的所有SQL脚本
-func (sm *StorageManager) ListSQLScripts(connID string) ([]SQLScript, error) {
+// 获取连接相关的所有脚本
+func (sm *StorageManager) ListScripts(connID string) ([]types.Script, error) {
 	sm.mu.RLock()
 	defer sm.mu.RUnlock()
 
-	sql := `SELECT 
+	query := `SELECT 
 		id, connection_id, group_id, name, description, content, favorite,
-		last_executed, execution_count, created_at, updated_at
-	FROM sql_scripts WHERE connection_id = ? ORDER BY name`
+		language, metadata, tags, enabled, owner, checksum, last_executed, last_run_status, execution_count, created_at, updated_at
+	FROM scripts WHERE connection_id = ? ORDER BY name`
 
-	rows, err := sm.db.Query(sql, connID)
+	rows, err := sm.db.Query(query, connID)
 	if err != nil {
-		return nil, fmt.Errorf("查询SQL脚本列表失败,连接ID: %s,错误: %v", connID, err)
+		return nil, fmt.Errorf("查询脚本列表失败,连接ID: %s,错误: %v", connID, err)
 	}
 	defer rows.Close()
 
-	var scripts []SQLScript
+	var scripts []types.Script
 	for rows.Next() {
-		var script SQLScript
+		var script types.Script
 
 		var lastExecuted, createdAt, updatedAt []byte
 		var favorite int
+		var language sql.NullString
+		var metadataStr sql.NullString
+		var tagsStr sql.NullString
+		var enabledInt sql.NullInt64
+		var owner sql.NullString
+		var checksum sql.NullString
+		var lastRunStatus sql.NullString
 
 		err := rows.Scan(
 			&script.ID, &script.ConnectionID, &script.GroupID,
 			&script.Name, &script.Description, &script.Content,
-			&favorite, &lastExecuted, &script.ExecutionCount,
+			&favorite, &language, &metadataStr, &tagsStr, &enabledInt, &owner, &checksum, &lastExecuted, &lastRunStatus, &script.ExecutionCount,
 			&createdAt, &updatedAt,
 		)
 
 		if err != nil {
-			return nil, fmt.Errorf("解析SQL脚本信息失败,错误: %v", err)
+			return nil, fmt.Errorf("解析脚本信息失败,错误: %v", err)
 		}
 
 		// 解析时间
@@ -552,8 +718,39 @@ func (sm *StorageManager) ListSQLScripts(connID string) ([]SQLScript, error) {
 		script.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", string(createdAt))
 		script.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", string(updatedAt))
 
-		// 转换布尔值
+		// 转换布尔/可空
 		script.Favorite = favorite != 0
+		if language.Valid {
+			script.Language = language.String
+		}
+		if owner.Valid {
+			script.Owner = owner.String
+		}
+		if checksum.Valid {
+			script.Checksum = checksum.String
+		}
+		if lastRunStatus.Valid {
+			script.LastRunStatus = lastRunStatus.String
+		}
+		if enabledInt.Valid {
+			script.Enabled = enabledInt.Int64 != 0
+		}
+
+		// metadata
+		if metadataStr.Valid && metadataStr.String != "" {
+			var m map[string]string
+			if err := json.Unmarshal([]byte(metadataStr.String), &m); err == nil {
+				script.Metadata = m
+			}
+		}
+
+		// tags
+		if tagsStr.Valid {
+			tags, err := unmarshalTags(tagsStr.String)
+			if err == nil {
+				script.Tags = tags
+			}
+		}
 
 		scripts = append(scripts, script)
 	}
@@ -561,6 +758,6 @@ func (sm *StorageManager) ListSQLScripts(connID string) ([]SQLScript, error) {
 	return scripts, nil
 } // 注:其他方法已移至相应的文件中实现
 // UpdateConnection, DeleteConnection, MoveConnection, GetAllConnections 在 connection_operations.go 中
-// CreateSQLScript, GetSQLScript, UpdateSQLScript, DeleteSQLScript, UpdateSQLScriptExecutionStats, ListSQLScripts 在 sql_scripts.go 中
+// CreateScript, GetScript, UpdateScript, DeleteScript, UpdateScriptExecutionStats, ListScripts 在 scripts.go 中
 // CreateConnectionGroup, GetConnectionGroup, UpdateConnectionGroup, DeleteConnectionGroup, MoveConnectionGroup, GetRootConnectionGroup, GetConnectionGroupTree 在 connection_groups.go 中
 // CreateScriptGroup, GetScriptGroup, UpdateScriptGroup, DeleteScriptGroup, MoveScriptGroup, GetRootScriptGroup, GetScriptGroupTree 在 script_groups.go 中

+ 8 - 56
service/internal/common/manager/storage/db_storage/script_groups.go

@@ -4,6 +4,8 @@ import (
 	db "database/sql"
 	"fmt"
 	"time"
+
+	"dbview/service/internal/common/manager/storage/types"
 )
 
 // CreateScriptGroup 创建脚本分组
@@ -33,7 +35,7 @@ func (sm *StorageManager) CreateScriptGroup(parentID, name, description string)
 		CreatedAt:   now,
 		UpdatedAt:   now,
 		Subgroups:   []ScriptGroup{},
-		Scripts:     []SQLScript{},
+		Scripts:     []types.Script{},
 	}
 
 	sql := `
@@ -99,7 +101,7 @@ func (sm *StorageManager) GetScriptGroup(groupID string, loadScripts bool, recur
 
 	// 如果需要加载脚本
 	if loadScripts {
-		scripts, err := sm.getScriptsByGroup(groupID)
+		scripts, err := sm.ListScriptsByGroup(groupID)
 		if err != nil {
 			return nil, err
 		}
@@ -159,7 +161,7 @@ func (sm *StorageManager) getScriptSubgroups(parentID string, loadScripts bool,
 
 		// 如果需要加载脚本
 		if loadScripts {
-			scripts, err := sm.getScriptsByGroup(group.ID)
+			scripts, err := sm.ListScriptsByGroup(group.ID)
 			if err != nil {
 				return nil, err
 			}
@@ -176,56 +178,6 @@ func (sm *StorageManager) getScriptSubgroups(parentID string, loadScripts bool,
 	return subgroups, nil
 }
 
-// getScriptsByGroup 获取分组下的脚本
-func (sm *StorageManager) getScriptsByGroup(groupID string) ([]SQLScript, error) {
-	sql := `SELECT
-		id, connection_id, group_id, name, description, 
-		content, favorite, last_executed, execution_count,
-		created_at, updated_at
-	FROM sql_scripts 
-	WHERE group_id = ?
-	ORDER BY name`
-
-	rows, err := sm.db.Query(sql, groupID)
-	if err != nil {
-		return nil, fmt.Errorf("查询分组脚本失败,分组ID: %s,错误: %v", groupID, err)
-	}
-	defer rows.Close()
-
-	var scripts []SQLScript
-	for rows.Next() {
-		var script SQLScript
-		var favorite int
-		var lastExecuted, createdAt, updatedAt []byte
-
-		err := rows.Scan(
-			&script.ID, &script.ConnectionID, &script.GroupID, &script.Name, &script.Description,
-			&script.Content, &favorite, &lastExecuted, &script.ExecutionCount,
-			&createdAt, &updatedAt,
-		)
-
-		if err != nil {
-			return nil, fmt.Errorf("扫描分组脚本行失败,分组ID: %s,错误: %v", groupID, err)
-		}
-
-		// 转换布尔值
-		script.Favorite = favorite != 0
-
-		// 转换时间
-		script.LastExecuted, _ = time.Parse("2006-01-02 15:04:05", string(lastExecuted))
-		script.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", string(createdAt))
-		script.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", string(updatedAt))
-
-		scripts = append(scripts, script)
-	}
-
-	if err = rows.Err(); err != nil {
-		return nil, fmt.Errorf("遍历分组脚本结果集失败,分组ID: %s,错误: %v", groupID, err)
-	}
-
-	return scripts, nil
-}
-
 // UpdateScriptGroup 更新脚本分组
 func (sm *StorageManager) UpdateScriptGroup(groupID, name, description string) (*ScriptGroup, error) {
 	// 步骤1:使用读锁检查分组是否存在
@@ -467,7 +419,7 @@ func (sm *StorageManager) getScriptGroup(groupID string) (*ScriptGroup, error) {
 
 	// 初始化子分组和脚本切片
 	group.Subgroups = []ScriptGroup{}
-	group.Scripts = []SQLScript{}
+	group.Scripts = []types.Script{}
 
 	return group, nil
 }
@@ -517,7 +469,7 @@ func (sm *StorageManager) deleteChildScriptGroupsInTransaction(tx *db.Tx, parent
 // deleteGroupScriptsInTransaction 在事务中删除分组下的所有脚本
 func (sm *StorageManager) deleteGroupScriptsInTransaction(tx *db.Tx, groupID string) error {
 	// 删除分组下的所有脚本
-	_, err := tx.Exec("DELETE FROM sql_scripts WHERE group_id = ?", groupID)
+	_, err := tx.Exec("DELETE FROM scripts WHERE group_id = ?", groupID)
 	if err != nil {
 		return fmt.Errorf("删除分组脚本失败,分组ID: %s,错误: %w", groupID, err)
 	}
@@ -563,7 +515,7 @@ func (sm *StorageManager) getOrCreateRootScriptGroupInTransaction(tx *db.Tx) (st
 // moveScriptsToRootInTransaction 在事务中将脚本移动到根分组
 func (sm *StorageManager) moveScriptsToRootInTransaction(tx *db.Tx, fromGroupID, toGroupID string) error {
 	// 将分组下的所有脚本移动到根分组
-	_, err := tx.Exec("UPDATE sql_scripts SET group_id = ? WHERE group_id = ?", toGroupID, fromGroupID)
+	_, err := tx.Exec("UPDATE scripts SET group_id = ? WHERE group_id = ?", toGroupID, fromGroupID)
 	if err != nil {
 		return fmt.Errorf("移动脚本到根分组失败,从分组ID: %s,到分组ID: %s,错误: %w", fromGroupID, toGroupID, err)
 	}

+ 405 - 0
service/internal/common/manager/storage/db_storage/scripts.go

@@ -0,0 +1,405 @@
+package db_storage
+
+import (
+	"database/sql"
+	"encoding/json"
+	"fmt"
+	"time"
+
+	"dbview/service/internal/common/manager/storage/types"
+)
+
+// CreateScript 创建新的脚本(通用)
+func (sm *StorageManager) CreateScript(connID, groupID, name, description, content string, favorite bool) (*types.Script, error) {
+	sm.mu.Lock()
+	defer sm.mu.Unlock()
+
+	// 验证连接ID是否存在
+	_, err := sm.GetConnection(connID)
+	if err != nil {
+		return nil, fmt.Errorf("验证连接ID失败: %w", err)
+	}
+
+	// 验证分组ID是否存在
+	group, err := sm.getScriptGroup(groupID)
+	if err != nil {
+		return nil, fmt.Errorf("验证分组ID失败: %w", err)
+	}
+	if group == nil {
+		return nil, fmt.Errorf("脚本分组不存在,分组ID: %s", groupID)
+	}
+
+	id := generateScriptID(groupID, name)
+	now := time.Now()
+
+	script := &types.Script{
+		ID:             id,
+		ConnectionID:   connID,
+		GroupID:        groupID,
+		Name:           name,
+		Description:    description,
+		Content:        content,
+		Favorite:       favorite,
+		ExecutionCount: 0,
+		Enabled:        true,
+		CreatedAt:      now,
+		UpdatedAt:      now,
+	}
+
+	favoriteInt := 0
+	if favorite {
+		favoriteInt = 1
+	}
+
+	// metadata and tags default to empty JSON
+	metaJSON := "{}"
+	tagsJSON := "[]"
+
+	sqlStr := `
+        INSERT INTO scripts (
+            id, connection_id, group_id, name, description,
+            content, favorite, language, metadata, tags, enabled, owner, checksum, execution_count, created_at, updated_at
+        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
+
+	_, err = sm.db.Exec(sqlStr,
+		id, connID, groupID, name, description,
+		content, favoriteInt, "", metaJSON, tagsJSON, 1, "", "", 0, now, now,
+	)
+
+	if err != nil {
+		return nil, fmt.Errorf("创建脚本失败,脚本名称: %s,错误: %v", name, err)
+	}
+
+	return script, nil
+}
+
+// GetScript 获取指定的脚本
+func (sm *StorageManager) GetScript(scriptID string) (*types.Script, error) {
+	sm.mu.RLock()
+	defer sm.mu.RUnlock()
+
+	sqlStr := `SELECT
+        id, connection_id, group_id, name, description,
+        content, favorite, language, metadata, tags, enabled, owner, checksum, last_executed, last_run_status, execution_count,
+        created_at, updated_at
+    FROM scripts
+    WHERE id = ?`
+
+	row := sm.db.QueryRow(sqlStr, scriptID)
+
+	script := &types.Script{}
+	var favorite int
+	var lastExecutedNull, createdAtNull, updatedAtNull sql.NullString
+	var language sql.NullString
+	var metadataStr sql.NullString
+	var tagsStr sql.NullString
+	var enabledInt sql.NullInt64
+	var owner sql.NullString
+	var checksum sql.NullString
+	var lastRunStatus sql.NullString
+
+	err := row.Scan(
+		&script.ID, &script.ConnectionID, &script.GroupID, &script.Name, &script.Description,
+		&script.Content, &favorite, &language, &metadataStr, &tagsStr, &enabledInt, &owner, &checksum, &lastExecutedNull, &lastRunStatus, &script.ExecutionCount,
+		&createdAtNull, &updatedAtNull,
+	)
+
+	if err != nil {
+		return nil, fmt.Errorf("查询脚本失败,脚本ID: %s,错误: %v", scriptID, err)
+	}
+
+	// 使用工具函数处理可空时间和可空字符串,减少重复逻辑
+	if t, ok := parseNullableTime(lastExecutedNull); ok {
+		script.LastExecuted = t
+	}
+	if t, ok := parseNullableTime(createdAtNull); ok {
+		script.CreatedAt = t
+	}
+	if t, ok := parseNullableTime(updatedAtNull); ok {
+		script.UpdatedAt = t
+	}
+
+	// 布尔和可空字段(使用 nullableStringToString 简化)
+	script.Favorite = favorite != 0
+	script.Language = nullableStringToString(language)
+	script.Owner = nullableStringToString(owner)
+	script.Checksum = nullableStringToString(checksum)
+	script.LastRunStatus = nullableStringToString(lastRunStatus)
+	if enabledInt.Valid {
+		script.Enabled = enabledInt.Int64 != 0
+	}
+
+	if s := nullableStringToString(metadataStr); s != "" {
+		var m map[string]string
+		if err := json.Unmarshal([]byte(s), &m); err == nil {
+			script.Metadata = m
+		}
+	}
+
+	if s := nullableStringToString(tagsStr); s != "" {
+		if tags, err := unmarshalTags(s); err == nil {
+			script.Tags = tags
+		}
+	}
+
+	return script, nil
+}
+
+// UpdateScript 更新脚本
+func (sm *StorageManager) UpdateScript(scriptID, name, description, content string, favorite bool) (*types.Script, error) {
+	// 先检查脚本是否存在
+	sm.mu.RLock()
+	_, err := sm.GetScript(scriptID)
+	sm.mu.RUnlock()
+	if err != nil {
+		return nil, err
+	}
+
+	sm.mu.Lock()
+	defer sm.mu.Unlock()
+
+	now := time.Now()
+	favoriteInt := 0
+	if favorite {
+		favoriteInt = 1
+	}
+
+	sqlStr := `
+        UPDATE scripts
+        SET name = ?, description = ?, content = ?, favorite = ?, updated_at = ?
+        WHERE id = ?`
+
+	result, err := sm.db.Exec(sqlStr, name, description, content, favoriteInt, now, scriptID)
+	if err != nil {
+		return nil, fmt.Errorf("更新脚本失败,脚本ID: %s,错误: %v", scriptID, err)
+	}
+
+	rowsAffected, err := result.RowsAffected()
+	if err != nil {
+		return nil, fmt.Errorf("获取脚本更新影响行数失败,脚本ID: %s,错误: %v", scriptID, err)
+	}
+	if rowsAffected == 0 {
+		return nil, fmt.Errorf("未找到要更新的脚本,脚本ID: %s", scriptID)
+	}
+
+	return sm.getScript(scriptID)
+}
+
+// DeleteScript 删除脚本
+func (sm *StorageManager) DeleteScript(scriptID string) error {
+	sm.mu.Lock()
+	defer sm.mu.Unlock()
+
+	sqlStr := "DELETE FROM scripts WHERE id = ?"
+	result, err := sm.db.Exec(sqlStr, scriptID)
+	if err != nil {
+		return fmt.Errorf("删除脚本失败,脚本ID: %s,错误: %v", scriptID, err)
+	}
+
+	rowsAffected, err := result.RowsAffected()
+	if err != nil {
+		return fmt.Errorf("获取脚本删除影响行数失败,脚本ID: %s,错误: %v", scriptID, err)
+	}
+	if rowsAffected == 0 {
+		return fmt.Errorf("未找到要删除的脚本,脚本ID: %s", scriptID)
+	}
+
+	return nil
+}
+
+// UpdateScriptExecutionStats 更新脚本执行统计
+func (sm *StorageManager) UpdateScriptExecutionStats(scriptID string) error {
+	sm.mu.Lock()
+	defer sm.mu.Unlock()
+
+	now := time.Now()
+	sqlStr := `
+        UPDATE scripts
+        SET last_executed = ?, execution_count = execution_count + 1
+        WHERE id = ?`
+
+	result, err := sm.db.Exec(sqlStr, now, scriptID)
+	if err != nil {
+		return fmt.Errorf("更新脚本执行统计失败,脚本ID: %s,错误: %v", scriptID, err)
+	}
+	rowsAffected, err := result.RowsAffected()
+	if err != nil {
+		return fmt.Errorf("获取脚本执行统计更新影响行数失败,脚本ID: %s,错误: %v", scriptID, err)
+	}
+	if rowsAffected == 0 {
+		return fmt.Errorf("未找到要更新执行统计的脚本,脚本ID: %s", scriptID)
+	}
+	return nil
+}
+
+// ListScriptsByGroup 获取指定分组的所有脚本
+func (sm *StorageManager) ListScriptsByGroup(groupID string) ([]types.Script, error) {
+	sm.mu.RLock()
+	defer sm.mu.RUnlock()
+
+	sqlStr := `SELECT
+        id, connection_id, group_id, name, description,
+        content, favorite, language, metadata, tags, enabled, owner, checksum, last_executed, last_run_status, execution_count,
+        created_at, updated_at
+    FROM scripts
+    WHERE group_id = ?
+    ORDER BY name`
+
+	rows, err := sm.db.Query(sqlStr, groupID)
+	if err != nil {
+		return nil, fmt.Errorf("查询分组脚本失败,分组ID: %s,错误: %v", groupID, err)
+	}
+	defer rows.Close()
+
+	var scripts []types.Script
+	for rows.Next() {
+		var script types.Script
+		var favorite int
+		var lastExecutedNull, createdAtNull, updatedAtNull sql.NullString
+		var language sql.NullString
+		var metadataStr sql.NullString
+		var tagsStr sql.NullString
+		var enabledInt sql.NullInt64
+		var owner sql.NullString
+		var checksum sql.NullString
+		var lastRunStatus sql.NullString
+
+		err := rows.Scan(
+			&script.ID, &script.ConnectionID, &script.GroupID, &script.Name, &script.Description,
+			&script.Content, &favorite, &language, &metadataStr, &tagsStr, &enabledInt, &owner, &checksum, &lastExecutedNull, &lastRunStatus, &script.ExecutionCount,
+			&createdAtNull, &updatedAtNull,
+		)
+		if err != nil {
+			return nil, fmt.Errorf("扫描分组脚本行失败,分组ID: %s,错误: %v", groupID, err)
+		}
+
+		if t, ok := parseNullableTime(lastExecutedNull); ok {
+			script.LastExecuted = t
+		}
+		if t, ok := parseNullableTime(createdAtNull); ok {
+			script.CreatedAt = t
+		}
+		if t, ok := parseNullableTime(updatedAtNull); ok {
+			script.UpdatedAt = t
+		}
+
+		script.Favorite = favorite != 0
+		script.Language = nullableStringToString(language)
+		script.Owner = nullableStringToString(owner)
+		script.Checksum = nullableStringToString(checksum)
+		script.LastRunStatus = nullableStringToString(lastRunStatus)
+		if enabledInt.Valid {
+			script.Enabled = enabledInt.Int64 != 0
+		}
+
+		if s := nullableStringToString(metadataStr); s != "" {
+			var m map[string]string
+			if err := json.Unmarshal([]byte(s), &m); err == nil {
+				script.Metadata = m
+			}
+		}
+		if s := nullableStringToString(tagsStr); s != "" {
+			if tags, err := unmarshalTags(s); err == nil {
+				script.Tags = tags
+			}
+		}
+
+		scripts = append(scripts, script)
+	}
+
+	if err = rows.Err(); err != nil {
+		return nil, fmt.Errorf("遍历分组脚本结果集失败,分组ID: %s,错误: %v", groupID, err)
+	}
+
+	return scripts, nil
+}
+
+// getScript 内部无锁版本 - 仅供内部更新方法使用
+func (sm *StorageManager) getScript(scriptID string) (*types.Script, error) {
+	sqlStr := `SELECT id, connection_id, group_id, name, description, content, favorite, language, metadata, tags, enabled, owner, checksum, execution_count, last_executed, last_run_status, created_at, updated_at FROM scripts WHERE id = ?`
+
+	row := sm.db.QueryRow(sqlStr, scriptID)
+	script := &types.Script{}
+
+	var favorite int
+	var lastExecutedNull, createdAtNull, updatedAtNull sql.NullString
+	var language sql.NullString
+	var metadataStr sql.NullString
+	var tagsStr sql.NullString
+	var enabledInt sql.NullInt64
+	var owner sql.NullString
+	var checksum sql.NullString
+	var lastRunStatus sql.NullString
+
+	err := row.Scan(
+		&script.ID, &script.ConnectionID, &script.GroupID, &script.Name,
+		&script.Description, &script.Content, &favorite, &language, &metadataStr, &tagsStr, &enabledInt, &owner, &checksum, &script.ExecutionCount, &lastExecutedNull, &lastRunStatus, &createdAtNull, &updatedAtNull,
+	)
+
+	if err != nil {
+		if err == sql.ErrNoRows {
+			return nil, fmt.Errorf("未找到脚本,脚本ID: %s", scriptID)
+		}
+		return nil, fmt.Errorf("查询脚本失败,脚本ID: %s,错误: %v", scriptID, err)
+	}
+
+	script.Favorite = favorite == 1
+	if lastExecutedNull.Valid {
+		if t, err := time.Parse(time.RFC3339Nano, lastExecutedNull.String); err == nil {
+			script.LastExecuted = t
+		} else if t2, err2 := time.Parse(time.RFC3339, lastExecutedNull.String); err2 == nil {
+			script.LastExecuted = t2
+		} else if t3, err3 := time.Parse("2006-01-02 15:04:05", lastExecutedNull.String); err3 == nil {
+			script.LastExecuted = t3
+		}
+	}
+	if createdAtNull.Valid {
+		if t, err := time.Parse(time.RFC3339Nano, createdAtNull.String); err == nil {
+			script.CreatedAt = t
+		} else if t2, err2 := time.Parse(time.RFC3339, createdAtNull.String); err2 == nil {
+			script.CreatedAt = t2
+		} else if t3, err3 := time.Parse("2006-01-02 15:04:05", createdAtNull.String); err3 == nil {
+			script.CreatedAt = t3
+		}
+	}
+	if updatedAtNull.Valid {
+		if t, err := time.Parse(time.RFC3339Nano, updatedAtNull.String); err == nil {
+			script.UpdatedAt = t
+		} else if t2, err2 := time.Parse(time.RFC3339, updatedAtNull.String); err2 == nil {
+			script.UpdatedAt = t2
+		} else if t3, err3 := time.Parse("2006-01-02 15:04:05", updatedAtNull.String); err3 == nil {
+			script.UpdatedAt = t3
+		}
+	}
+
+	if language.Valid {
+		script.Language = language.String
+	}
+	if owner.Valid {
+		script.Owner = owner.String
+	}
+	if checksum.Valid {
+		script.Checksum = checksum.String
+	}
+	if lastRunStatus.Valid {
+		script.LastRunStatus = lastRunStatus.String
+	}
+	if enabledInt.Valid {
+		script.Enabled = enabledInt.Int64 != 0
+	}
+
+	if metadataStr.Valid && metadataStr.String != "" {
+		var m map[string]string
+		if err := json.Unmarshal([]byte(metadataStr.String), &m); err == nil {
+			script.Metadata = m
+		}
+	}
+	if tagsStr.Valid {
+		tags, err := unmarshalTags(tagsStr.String)
+		if err == nil {
+			script.Tags = tags
+		}
+	}
+
+	return script, nil
+}

+ 2 - 300
service/internal/common/manager/storage/db_storage/sql_scripts.go

@@ -1,302 +1,4 @@
 package db_storage
 
-import (
-	"fmt"
-	"time"
-)
-
-// CreateSQLScript 创建新的SQL脚本
-func (sm *StorageManager) CreateSQLScript(connID, groupID, name, description, content string, favorite bool) (*SQLScript, error) {
-	sm.mu.Lock()
-	defer sm.mu.Unlock()
-
-	// 验证连接ID是否存在
-	conn, err := sm.getConnection(connID)
-	if err != nil {
-		return nil, fmt.Errorf("验证连接ID失败: %w", err)
-	}
-	if conn == nil {
-		return nil, fmt.Errorf("数据库连接不存在,连接ID: %s", connID)
-	}
-
-	// 验证分组ID是否存在
-	group, err := sm.getScriptGroup(groupID)
-	if err != nil {
-		return nil, fmt.Errorf("验证分组ID失败: %w", err)
-	}
-	if group == nil {
-		return nil, fmt.Errorf("脚本分组不存在,分组ID: %s", groupID)
-	}
-
-	id := generateScriptID(groupID, name)
-	now := time.Now()
-
-	script := &SQLScript{
-		ID:             id,
-		ConnectionID:   connID,
-		GroupID:        groupID,
-		Name:           name,
-		Description:    description,
-		Content:        content,
-		Favorite:       favorite,
-		ExecutionCount: 0,
-		CreatedAt:      now,
-		UpdatedAt:      now,
-	}
-
-	favoriteInt := 0
-	if favorite {
-		favoriteInt = 1
-	}
-
-	sql := `
-		INSERT INTO sql_scripts (
-			id, connection_id, group_id, name, description, 
-			content, favorite, execution_count, created_at, updated_at
-		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
-
-	_, err = sm.db.Exec(sql,
-		id, connID, groupID, name, description,
-		content, favoriteInt, 0, now, now,
-	)
-
-	if err != nil {
-		return nil, fmt.Errorf("创建SQL脚本失败,脚本名称: %s,错误: %v", name, err)
-	}
-
-	return script, nil
-}
-
-// GetSQLScript 获取指定的SQL脚本
-func (sm *StorageManager) GetSQLScript(scriptID string) (*SQLScript, error) {
-	sm.mu.RLock()
-	defer sm.mu.RUnlock()
-
-	sql := `SELECT
-		id, connection_id, group_id, name, description, 
-		content, favorite, last_executed, execution_count,
-		created_at, updated_at
-	FROM sql_scripts 
-	WHERE id = ?`
-
-	row := sm.db.QueryRow(sql, scriptID)
-
-	script := &SQLScript{}
-	var favorite int
-	var lastExecuted, createdAt, updatedAt []byte
-
-	err := row.Scan(
-		&script.ID, &script.ConnectionID, &script.GroupID, &script.Name, &script.Description,
-		&script.Content, &favorite, &lastExecuted, &script.ExecutionCount,
-		&createdAt, &updatedAt,
-	)
-
-	if err != nil {
-		return nil, fmt.Errorf("查询SQL脚本失败,脚本ID: %s,错误: %v", scriptID, err)
-	}
-
-	// 转换时间
-	if lastExecuted != nil {
-		script.LastExecuted, _ = time.Parse("2006-01-02 15:04:05", string(lastExecuted))
-	}
-	script.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", string(createdAt))
-	script.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", string(updatedAt))
-
-	// 转换布尔值
-	script.Favorite = favorite != 0
-
-	return script, nil
-}
-
-// UpdateSQLScript 更新SQL脚本
-func (sm *StorageManager) UpdateSQLScript(scriptID, name, description, content string, favorite bool) (*SQLScript, error) {
-	// 首先检查脚本是否存在(使用读锁)
-	sm.mu.RLock()
-	_, err := sm.GetSQLScript(scriptID)
-	sm.mu.RUnlock()
-	if err != nil {
-		return nil, err
-	}
-
-	// 现在获取写锁进行更新
-	sm.mu.Lock()
-	defer sm.mu.Unlock()
-
-	now := time.Now()
-	favoriteInt := 0
-	if favorite {
-		favoriteInt = 1
-	}
-
-	sql := `
-		UPDATE sql_scripts
-		SET name = ?, description = ?,
-			content = ?, favorite = ?, updated_at = ?
-		WHERE id = ?`
-
-	result, err := sm.db.Exec(sql,
-		name, description,
-		content, favoriteInt, now,
-		scriptID,
-	)
-
-	if err != nil {
-		return nil, fmt.Errorf("更新SQL脚本失败,脚本ID: %s,错误: %v", scriptID, err)
-	}
-
-	rowsAffected, err := result.RowsAffected()
-	if err != nil {
-		return nil, fmt.Errorf("获取SQL脚本更新影响行数失败,脚本ID: %s,错误: %v", scriptID, err)
-	}
-
-	if rowsAffected == 0 {
-		return nil, fmt.Errorf("未找到要更新的SQL脚本,脚本ID: %s", scriptID)
-	}
-
-	// 使用内部无锁函数获取更新后的脚本信息
-	return sm.getSQLScript(scriptID)
-}
-
-// DeleteSQLScript 删除SQL脚本
-func (sm *StorageManager) DeleteSQLScript(scriptID string) error {
-	sm.mu.Lock()
-	defer sm.mu.Unlock()
-
-	sql := "DELETE FROM sql_scripts WHERE id = ?"
-
-	result, err := sm.db.Exec(sql, scriptID)
-	if err != nil {
-		return fmt.Errorf("删除SQL脚本失败,脚本ID: %s,错误: %v", scriptID, err)
-	}
-
-	rowsAffected, err := result.RowsAffected()
-	if err != nil {
-		return fmt.Errorf("获取SQL脚本删除影响行数失败,脚本ID: %s,错误: %v", scriptID, err)
-	}
-
-	if rowsAffected == 0 {
-		return fmt.Errorf("未找到要删除的SQL脚本,脚本ID: %s", scriptID)
-	}
-
-	return nil
-}
-
-// UpdateSQLScriptExecutionStats 更新SQL脚本执行统计
-func (sm *StorageManager) UpdateSQLScriptExecutionStats(scriptID string) error {
-	sm.mu.Lock()
-	defer sm.mu.Unlock()
-
-	now := time.Now()
-
-	sql := `
-		UPDATE sql_scripts
-		SET last_executed = ?,
-			execution_count = execution_count + 1
-		WHERE id = ?`
-
-	result, err := sm.db.Exec(sql, now, scriptID)
-	if err != nil {
-		return fmt.Errorf("更新SQL脚本执行统计失败,脚本ID: %s,错误: %v", scriptID, err)
-	}
-
-	rowsAffected, err := result.RowsAffected()
-	if err != nil {
-		return fmt.Errorf("获取SQL脚本执行统计更新影响行数失败,脚本ID: %s,错误: %v", scriptID, err)
-	}
-
-	if rowsAffected == 0 {
-		return fmt.Errorf("未找到要更新执行统计的SQL脚本,脚本ID: %s", scriptID)
-	}
-
-	return nil
-}
-
-// ListSQLScriptsByGroup 获取指定分组的所有SQL脚本
-func (sm *StorageManager) ListSQLScriptsByGroup(groupID string) ([]SQLScript, error) {
-	sm.mu.RLock()
-	defer sm.mu.RUnlock()
-
-	sql := `SELECT
-		id, connection_id, group_id, name, description, 
-		content, favorite, last_executed, execution_count,
-		created_at, updated_at
-	FROM sql_scripts 
-	WHERE group_id = ?
-	ORDER BY name`
-
-	rows, err := sm.db.Query(sql, groupID)
-	if err != nil {
-		return nil, fmt.Errorf("查询分组SQL脚本失败,分组ID: %s,错误: %v", groupID, err)
-	}
-	defer rows.Close()
-
-	var scripts []SQLScript
-	for rows.Next() {
-		var script SQLScript
-
-		var favorite int
-		var lastExecuted, createdAt, updatedAt []byte
-
-		err := rows.Scan(
-			&script.ID, &script.ConnectionID, &script.GroupID, &script.Name, &script.Description,
-			&script.Content, &favorite, &lastExecuted, &script.ExecutionCount,
-			&createdAt, &updatedAt,
-		)
-
-		if err != nil {
-			return nil, fmt.Errorf("扫描分组SQL脚本行失败,分组ID: %s,错误: %v", groupID, err)
-		}
-
-		// 转换时间
-		if lastExecuted != nil {
-			script.LastExecuted, _ = time.Parse("2006-01-02 15:04:05", string(lastExecuted))
-		}
-		script.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", string(createdAt))
-		script.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", string(updatedAt))
-
-		// 转换布尔值
-		script.Favorite = favorite != 0
-
-		scripts = append(scripts, script)
-	}
-
-	if err = rows.Err(); err != nil {
-		return nil, fmt.Errorf("遍历分组SQL脚本结果集失败,分组ID: %s,错误: %v", groupID, err)
-	}
-
-	return scripts, nil
-}
-
-// getSQLScript 内部无锁版本 - 仅供内部更新方法使用
-func (sm *StorageManager) getSQLScript(scriptID string) (*SQLScript, error) {
-	sql := `SELECT id, connection_id, group_id, name, description, content, favorite, execution_count, last_executed, created_at, updated_at FROM sql_scripts WHERE id = ?`
-
-	row := sm.db.QueryRow(sql, scriptID)
-	script := &SQLScript{}
-
-	var favorite int
-	var lastExecuted, createdAt, updatedAt []byte
-
-	err := row.Scan(
-		&script.ID, &script.ConnectionID, &script.GroupID, &script.Name,
-		&script.Description, &script.Content, &favorite, &script.ExecutionCount,
-		&lastExecuted, &createdAt, &updatedAt,
-	)
-
-	if err != nil {
-		if err.Error() == "sql: no rows in result set" {
-			return nil, fmt.Errorf("未找到SQL脚本,脚本ID: %s", scriptID)
-		}
-		return nil, fmt.Errorf("查询SQL脚本失败,脚本ID: %s,错误: %v", scriptID, err)
-	}
-
-	// 转换布尔值
-	script.Favorite = favorite == 1
-
-	// 解析时间
-	script.LastExecuted, _ = time.Parse("2006-01-02 15:04:05", string(lastExecuted))
-	script.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", string(createdAt))
-	script.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", string(updatedAt))
-
-	return script, nil
-}
+// This file has been intentionally emptied after moving implementations to `scripts.go`.
+// Keeping this file avoids accidental deletes in some workflows.

+ 32 - 0
service/internal/common/manager/storage/db_storage/utils.go

@@ -0,0 +1,32 @@
+package db_storage
+
+import (
+	"database/sql"
+	"time"
+)
+
+// nullableStringToString 将 sql.NullString 转换为普通字符串(NULL -> "")
+func nullableStringToString(ns sql.NullString) string {
+	if ns.Valid {
+		return ns.String
+	}
+	return ""
+}
+
+// parseNullableTime 尝试解析 sql.NullString 中的时间字符串,
+// 优先使用 RFC3339Nano,再退回 RFC3339,最后退回旧布局,成功则返回(t, true)
+func parseNullableTime(ns sql.NullString) (time.Time, bool) {
+	if !ns.Valid {
+		return time.Time{}, false
+	}
+	if t, err := time.Parse(time.RFC3339Nano, ns.String); err == nil {
+		return t, true
+	}
+	if t, err := time.Parse(time.RFC3339, ns.String); err == nil {
+		return t, true
+	}
+	if t, err := time.Parse("2006-01-02 15:04:05", ns.String); err == nil {
+		return t, true
+	}
+	return time.Time{}, false
+}

+ 15 - 14
service/internal/common/manager/storage/interface.go

@@ -7,12 +7,14 @@ import (
 // StorageInterface 定义存储接口
 type StorageInterface interface {
 	// 连接管理
-	CreateConnection(groupID, name, description, dbType, dbVersion, server string, port int, username, password, database, connectionString string, useSSHTunnel bool, color string, autoConnect bool, displayOrder int) (*types.DBConnection, error)
-	GetConnection(connID string) (*types.DBConnection, error)
-	UpdateConnection(connID string, req *types.UpdateConnectionRequest) (*types.DBConnection, error)
+	// CreateConnection 新增第一个参数 kind,表示连接类别:"database" | "server" | "other"。
+	// type 表示具体子类型(例如 mysql/postgres 或 ssh),version 表示版本号或次级信息。
+	CreateConnection(groupID, name, description, kind, typ, version, server string, port int, username, password, database, connectionString string, useSSHTunnel bool, color string, autoConnect bool, displayOrder int) (*types.ConnectionWithDetails, error)
+	GetConnection(connID string) (*types.ConnectionWithDetails, error)
+	UpdateConnection(connID string, req *types.UpdateConnectionRequest) (*types.ConnectionWithDetails, error)
 	DeleteConnection(connID string) error
-	MoveConnection(connID, targetGroupID string) (*types.DBConnection, error)
-	GetAllConnections() ([]types.DBConnection, error)
+	MoveConnection(connID, targetGroupID string) (*types.ConnectionWithDetails, error)
+	GetAllConnections() ([]types.ConnectionWithDetails, error)
 
 	// 连接分组管理
 	CreateConnectionGroup(parentID, name, description, icon string, displayOrder int) (*types.ConnectionGroup, error)
@@ -23,13 +25,13 @@ type StorageInterface interface {
 	GetRootConnectionGroup(loadConnections bool, recursive bool) (*types.ConnectionGroup, error)
 	GetConnectionGroupTree(loadConnections bool) (*types.ConnectionGroup, error)
 
-	// SQL脚本管理
-	CreateSQLScript(connectionID, groupID, name, description, content string, favorite bool) (*types.SQLScript, error)
-	GetSQLScript(scriptID string) (*types.SQLScript, error)
-	UpdateSQLScript(scriptID, name, description, content string, favorite bool) (*types.SQLScript, error)
-	DeleteSQLScript(scriptID string) error
-	UpdateSQLScriptExecutionStats(scriptID string) error
-	ListSQLScripts(connID string) ([]types.SQLScript, error)
+	// Script 管理(通用脚本)
+	CreateScript(connectionID, groupID, name, description, content string, favorite bool) (*types.Script, error)
+	GetScript(scriptID string) (*types.Script, error)
+	UpdateScript(scriptID, name, description, content string, favorite bool) (*types.Script, error)
+	DeleteScript(scriptID string) error
+	UpdateScriptExecutionStats(scriptID string) error
+	ListScripts(connID string) ([]types.Script, error)
 
 	// 脚本分组管理
 	CreateScriptGroup(parentID, name, description string) (*types.ScriptGroup, error)
@@ -45,7 +47,6 @@ type StorageInterface interface {
 }
 
 // 类型别名,保持向后兼容
-type DBConnection = types.DBConnection
 type ConnectionGroup = types.ConnectionGroup
-type SQLScript = types.SQLScript
+type Script = types.Script
 type ScriptGroup = types.ScriptGroup

+ 98 - 54
service/internal/common/manager/storage/types/types.go

@@ -2,57 +2,93 @@ package types
 
 import "time"
 
-// DBConnection 数据库连接
-type DBConnection struct {
-	ID               string      `json:"id" db:"id"`
-	GroupID          string      `json:"group_id" db:"group_id"`
-	Name             string      `json:"name" db:"name"`
-	Description      string      `json:"description" db:"description"`
-	DBType           string      `json:"db_type" db:"db_type"`
-	DBVersion        string      `json:"db_version" db:"db_version"`
-	Server           string      `json:"server" db:"server"`
-	Port             int         `json:"port" db:"port"`
-	Database         string      `json:"database" db:"database"`
-	Username         string      `json:"username" db:"username"`
-	Password         string      `json:"password" db:"password_encrypted"`
-	UseSSHTunnel     bool        `json:"use_ssh_tunnel" db:"use_ssh_tunnel"`
-	ConnectionString string      `json:"connection_string" db:"connection_string"`
-	Color            string      `json:"color" db:"color"`
-	LastConnected    time.Time   `json:"last_connected" db:"last_connected"`
-	AutoConnect      bool        `json:"auto_connect" db:"auto_connect"`
-	DisplayOrder     int         `json:"display_order" db:"display_order"`
-	CreatedAt        time.Time   `json:"created_at" db:"created_at"`
-	UpdatedAt        time.Time   `json:"updated_at" db:"updated_at"`
-	Scripts          []SQLScript `json:"scripts" db:"scripts"`
+// Connection 基础通用连接信息
+type Connection struct {
+	ID          string `json:"id" db:"id"`
+	GroupID     string `json:"group_id" db:"group_id"`
+	Name        string `json:"name" db:"name"`
+	Description string `json:"description" db:"description"`
+	// Kind 表示连接的类别:"database" | "server" | "other"
+	Kind string `json:"kind" db:"kind"`
+	// Type/Version 已移到特化 detail 表(如 db_connections / server_connections)
+	Color        string    `json:"color" db:"color"`
+	AutoConnect  bool      `json:"auto_connect" db:"auto_connect"`
+	DisplayOrder int       `json:"display_order" db:"display_order"`
+	CreatedAt    time.Time `json:"created_at" db:"created_at"`
+	UpdatedAt    time.Time `json:"updated_at" db:"updated_at"`
+}
+
+// DBConnectionDetail 数据库连接特有字段
+type DBConnectionDetail struct {
+	ConnectionID        string    `json:"connection_id" db:"connection_id"`
+	Type                string    `json:"type" db:"type"`
+	Version             string    `json:"version" db:"version"`
+	Server              string    `json:"server" db:"server"`
+	Port                int       `json:"port" db:"port"`
+	Username            string    `json:"username" db:"username"`
+	Password            string    `json:"password" db:"password_encrypted"`
+	DatabaseName        string    `json:"database" db:"database"`
+	ConnectionString    string    `json:"connection_string" db:"connection_string"`
+	UseSSHTunnel        bool      `json:"use_ssh_tunnel" db:"use_ssh_tunnel"`
+	SSHTunnelConnection string    `json:"ssh_tunnel_connection_id" db:"ssh_tunnel_connection_id"`
+	LastConnected       time.Time `json:"last_connected" db:"last_connected"`
+}
+
+// ServerConnectionDetail 服务器/主机类连接特有字段
+type ServerConnectionDetail struct {
+	ConnectionID string `json:"connection_id" db:"connection_id"`
+	Type         string `json:"type" db:"type"`
+	Version      string `json:"version" db:"version"`
+	Server       string `json:"server" db:"server"`
+	Port         int    `json:"port" db:"port"`
+	Username     string `json:"username" db:"username"`
+	AuthType     string `json:"auth_type" db:"auth_type"`
+	PrivateKey   string `json:"private_key" db:"private_key"`
+	UseSudo      bool   `json:"use_sudo" db:"use_sudo"`
+}
+
+// ConnectionWithDetails 聚合返回,用于 API 层
+type ConnectionWithDetails struct {
+	Connection
+	DBDetail     *DBConnectionDetail     `json:"db_detail,omitempty" db:"db_detail"`
+	ServerDetail *ServerConnectionDetail `json:"server_detail,omitempty" db:"server_detail"`
+	Scripts      []Script                `json:"scripts" db:"scripts"`
 }
 
 // ConnectionGroup 连接分组
 type ConnectionGroup struct {
-	ID           string            `json:"id" db:"id"`
-	ParentID     string            `json:"parent_id" db:"parent_id"`
-	Name         string            `json:"name" db:"name"`
-	Description  string            `json:"description" db:"description"`
-	DisplayOrder int               `json:"display_order" db:"display_order"`
-	Icon         string            `json:"icon" db:"icon"`
-	CreatedAt    time.Time         `json:"created_at" db:"created_at"`
-	UpdatedAt    time.Time         `json:"updated_at" db:"updated_at"`
-	Subgroups    []ConnectionGroup `json:"subgroups" db:"subgroups"`
-	Connections  []DBConnection    `json:"connections" db:"connections"`
+	ID           string                  `json:"id" db:"id"`
+	ParentID     string                  `json:"parent_id" db:"parent_id"`
+	Name         string                  `json:"name" db:"name"`
+	Description  string                  `json:"description" db:"description"`
+	DisplayOrder int                     `json:"display_order" db:"display_order"`
+	Icon         string                  `json:"icon" db:"icon"`
+	CreatedAt    time.Time               `json:"created_at" db:"created_at"`
+	UpdatedAt    time.Time               `json:"updated_at" db:"updated_at"`
+	Subgroups    []ConnectionGroup       `json:"subgroups" db:"subgroups"`
+	Connections  []ConnectionWithDetails `json:"connections" db:"connections"`
 }
 
-// SQLScript SQL脚本
-type SQLScript struct {
-	ID             string    `json:"id" db:"id"`
-	ConnectionID   string    `json:"connection_id" db:"connection_id"`
-	GroupID        string    `json:"group_id" db:"group_id"`
-	Name           string    `json:"name" db:"name"`
-	Description    string    `json:"description" db:"description"`
-	Content        string    `json:"content" db:"content"`
-	Favorite       bool      `json:"favorite" db:"favorite"`
-	LastExecuted   time.Time `json:"last_executed" db:"last_executed"`
-	ExecutionCount int       `json:"execution_count" db:"execution_count"`
-	CreatedAt      time.Time `json:"created_at" db:"created_at"`
-	UpdatedAt      time.Time `json:"updated_at" db:"updated_at"`
+// Script 存储的脚本(通用)
+type Script struct {
+	ID             string            `json:"id" db:"id"`
+	ConnectionID   string            `json:"connection_id" db:"connection_id"`
+	GroupID        string            `json:"group_id" db:"group_id"`
+	Name           string            `json:"name" db:"name"`
+	Description    string            `json:"description" db:"description"`
+	Content        string            `json:"content" db:"content"`
+	Favorite       bool              `json:"favorite" db:"favorite"`
+	Language       string            `json:"language" db:"language"`
+	Metadata       map[string]string `json:"metadata,omitempty" db:"metadata"`
+	Tags           []string          `json:"tags,omitempty" db:"tags"`
+	Enabled        bool              `json:"enabled" db:"enabled"`
+	Owner          string            `json:"owner" db:"owner"`
+	Checksum       string            `json:"checksum" db:"checksum"`
+	LastExecuted   time.Time         `json:"last_executed" db:"last_executed"`
+	LastRunStatus  string            `json:"last_run_status" db:"last_run_status"`
+	ExecutionCount int               `json:"execution_count" db:"execution_count"`
+	CreatedAt      time.Time         `json:"created_at" db:"created_at"`
+	UpdatedAt      time.Time         `json:"updated_at" db:"updated_at"`
 }
 
 // ScriptGroup 脚本分组
@@ -64,17 +100,20 @@ type ScriptGroup struct {
 	CreatedAt   time.Time     `json:"created_at" db:"created_at"`
 	UpdatedAt   time.Time     `json:"updated_at" db:"updated_at"`
 	Subgroups   []ScriptGroup `json:"subgroups" db:"subgroups"`
-	Scripts     []SQLScript   `json:"scripts" db:"scripts"`
+	Scripts     []Script      `json:"scripts" db:"scripts"`
 }
 
 // UpdateConnectionRequest 更新连接请求结构体
 // 使用指针类型来区分零值和未提供的字段
 type UpdateConnectionRequest struct {
-	GroupID          *string `json:"group_id,omitempty"`
-	Name             *string `json:"name,omitempty"`
-	Description      *string `json:"description,omitempty"`
-	DBType           *string `json:"db_type,omitempty"`
-	DBVersion        *string `json:"db_version,omitempty"`
+	GroupID     *string `json:"group_id,omitempty"`
+	Name        *string `json:"name,omitempty"`
+	Description *string `json:"description,omitempty"`
+	Kind        *string `json:"kind,omitempty"`
+	Type        *string `json:"type,omitempty"`
+	Version     *string `json:"version,omitempty"`
+	// 已移除旧兼容字段 `db_type`/`db_version`,请使用 `type`/`version`
+	// DB-specific fields
 	Server           *string `json:"server,omitempty"`
 	Port             *int    `json:"port,omitempty"`
 	Username         *string `json:"username,omitempty"`
@@ -82,7 +121,12 @@ type UpdateConnectionRequest struct {
 	Database         *string `json:"database,omitempty"`
 	ConnectionString *string `json:"connection_string,omitempty"`
 	UseSSHTunnel     *bool   `json:"use_ssh_tunnel,omitempty"`
-	Color            *string `json:"color,omitempty"`
-	AutoConnect      *bool   `json:"auto_connect,omitempty"`
-	DisplayOrder     *int    `json:"display_order,omitempty"`
+	SSHTunnelConnID  *string `json:"ssh_tunnel_connection_id,omitempty"`
+	// Server-specific fields
+	AuthType     *string `json:"auth_type,omitempty"`
+	PrivateKey   *string `json:"private_key,omitempty"`
+	UseSudo      *bool   `json:"use_sudo,omitempty"`
+	Color        *string `json:"color,omitempty"`
+	AutoConnect  *bool   `json:"auto_connect,omitempty"`
+	DisplayOrder *int    `json:"display_order,omitempty"`
 }

+ 12 - 12
service/internal/common/response/response.go

@@ -37,12 +37,12 @@ const (
 	CodeLoadFailed    = 3004 // 加载失败
 	CodeDeleteFailed  = 3005 // 删除失败
 
-	// SQL文件相关
-	CodeSQLFileNotExist  = 4001 // SQL文件不存在
-	CodeSQLFileExist     = 4002 // SQL文件已存在
-	CodeSQLFileSaveErr   = 4003 // SQL文件保存失败
-	CodeSQLFileLoadErr   = 4004 // SQL文件加载失败
-	CodeSQLFileDeleteErr = 4005 // SQL文件删除失败
+	// 脚本 (Script) 相关
+	CodeScriptNotExist  = 4001 // 脚本不存在
+	CodeScriptExist     = 4002 // 脚本已存在
+	CodeScriptSaveErr   = 4003 // 脚本保存失败
+	CodeScriptLoadErr   = 4004 // 脚本加载失败
+	CodeScriptDeleteErr = 4005 // 脚本删除失败
 
 	// 其他可扩展
 )
@@ -87,12 +87,12 @@ var (
 	ErrLoadFailed    = NewAppError(CodeLoadFailed, "加载失败")
 	ErrDeleteFailed  = NewAppError(CodeDeleteFailed, "删除失败")
 
-	// SQL文件相关
-	ErrSQLFileNotExist  = NewAppError(CodeSQLFileNotExist, "SQL文件不存在")
-	ErrSQLFileExist     = NewAppError(CodeSQLFileExist, "SQL文件已存在")
-	ErrSQLFileSaveErr   = NewAppError(CodeSQLFileSaveErr, "SQL文件保存失败")
-	ErrSQLFileLoadErr   = NewAppError(CodeSQLFileLoadErr, "SQL文件加载失败")
-	ErrSQLFileDeleteErr = NewAppError(CodeSQLFileDeleteErr, "SQL文件删除失败")
+	// 脚本 (Script) 相关
+	ErrScriptNotExist  = NewAppError(CodeScriptNotExist, "脚本不存在")
+	ErrScriptExist     = NewAppError(CodeScriptExist, "脚本已存在")
+	ErrScriptSaveErr   = NewAppError(CodeScriptSaveErr, "脚本保存失败")
+	ErrScriptLoadErr   = NewAppError(CodeScriptLoadErr, "脚本加载失败")
+	ErrScriptDeleteErr = NewAppError(CodeScriptDeleteErr, "脚本删除失败")
 )
 
 // 响应辅助函数

+ 3 - 2
service/internal/config/README.md

@@ -31,8 +31,9 @@ retention_days = 90
 # 审计缓冲区大小
 buffer_size = 1000
 
+# Storage 配置
+# `sql_base_dir` 为脚本(Script)在文件存储模式下的基础目录,默认 `./DBconfig/sqlfiles`
 [storage]
-# SQL文件基础目录
 sql_base_dir = "./DBconfig/sqlfiles"
 # 数据文件路径
 data_file = "./DBconfig/data.toml"
@@ -110,7 +111,7 @@ if cfg.Audit.Enabled {
 
 - `type`: 存储类型 ("file" 或 "db")
 - `config_path`: 配置文件路径 (文件存储使用)
-- `sql_base_dir`: SQL文件基础目录 (文件存储使用)
+- `sql_base_dir`: 脚本(Script)基础目录 (文件存储使用)
 - `database_path`: 数据库文件路径 (数据库存储使用)
 - `data_file`: 数据文件路径 (向后兼容)
 

+ 1 - 1
service/internal/config/config.go

@@ -47,7 +47,7 @@ type StorageConfig struct {
 
 	// 文件存储配置
 	ConfigPath string `toml:"config_path"`  // 配置文件路径 (用于文件存储)
-	SQLBaseDir string `toml:"sql_base_dir"` // SQL文件基础目录 (用于文件存储)
+	SQLBaseDir string `toml:"sql_base_dir"` // 脚本(Script)基础目录 (用于文件存储)
 
 	// 数据库存储配置
 	DatabasePath string `toml:"database_path"` // 数据库文件路径 (用于数据库存储)

+ 22 - 22
service/internal/config/data.toml

@@ -33,8 +33,8 @@ subfolders = []
 id = 'dev_mysql1'
 name = '开发MySQL'
 description = '开发环境MySQL数据库'
-db_type = 'mysql'
-db_version = '8.0'
+type = 'mysql'
+version = '8.0'
 host = 'dev-mysql.local'
 port = 3306
 username = 'dev_user'
@@ -45,7 +45,7 @@ folder_path = '/项目A1/项目A1_1'
 last_used = 2025-08-21T09:51:57.1565975+08:00
 created_time = 2025-07-09T11:30:00+08:00
 tags = ['mysql', '开发', 'app']
-sql_files = []
+scripts = []
 
 [[folders]]
 name = '开发环境'
@@ -69,8 +69,8 @@ subfolders = []
 id = 'a1b2c3d4'
 name = '配置中心'
 description = '应用配置管理数据库'
-db_type = 'mysql'
-db_version = '8.0'
+type = 'mysql'
+version = '8.0'
 host = 'config-db.example.com'
 port = 3306
 username = 'config_user'
@@ -81,7 +81,7 @@ folder_path = '/开发环境/测试环境'
 last_used = 2025-08-21T09:17:41.6906649+08:00
 created_time = 2025-07-09T08:15:00+08:00
 tags = ['配置', '公共', '服务']
-sql_files = []
+scripts = []
 
 [[folders]]
 name = '项目B'
@@ -96,8 +96,8 @@ subfolders = []
 id = 'dev_redis1'
 name = '开发Redis'
 description = '开发环境Redis缓存'
-db_type = 'redis'
-db_version = '7.0'
+type = 'redis'
+version = '7.0'
 host = 'dev-redis.local'
 port = 6379
 username = ''
@@ -108,14 +108,14 @@ folder_path = '/项目B'
 last_used = 2025-08-22T02:52:38.3990331+08:00
 created_time = 2025-07-09T11:45:00+08:00
 tags = ['redis', '缓存', '开发']
-sql_files = []
+scripts = []
 
 [[root_connections]]
 id = '06feaf0d'
 name = 'xugu'
 description = ''
-db_type = 'xugu'
-db_version = ''
+type = 'xugu'
+version = ''
 host = '10.28.20.105'
 port = 5130
 username = 'SYSDBA'
@@ -127,7 +127,7 @@ last_used = 2025-07-16T16:17:57.362633+08:00
 created_time = 2025-07-14T09:54:49.1276253+08:00
 tags = []
 
-[[root_connections.sql_files]]
+[[root_connections.scripts]]
 id = 'd68b69ab'
 db_conn_id = '06feaf0d'
 name = '新查询_2025-07-14T08-37-26.sql'
@@ -136,7 +136,7 @@ tags = ['query']
 last_modified = 2025-07-14T16:40:35.0592193+08:00
 created_time = 2025-07-14T16:37:26.3195453+08:00
 
-[[root_connections.sql_files]]
+[[root_connections.scripts]]
 id = '6e79e134'
 db_conn_id = '06feaf0d'
 name = '新查询_2025-07-16T08-10-14.sql'
@@ -145,7 +145,7 @@ tags = ['query']
 last_modified = 2025-07-16T16:13:58.6059498+08:00
 created_time = 2025-07-16T16:10:14.9565236+08:00
 
-[[root_connections.sql_files]]
+[[root_connections.scripts]]
 id = 'b99ddcfb'
 db_conn_id = '06feaf0d'
 name = '新查询_2025-07-16T08-19-03.sql'
@@ -158,8 +158,8 @@ created_time = 2025-07-16T16:19:03.4709372+08:00
 id = '04d789fe'
 name = 'MySQL-开发库'
 description = ''
-db_type = 'mysql'
-db_version = ''
+type = 'mysql'
+version = ''
 host = '10.28.25.127'
 port = 3306
 username = 'root'
@@ -171,7 +171,7 @@ last_used = 2025-08-19T16:08:20.7645315+08:00
 created_time = 2025-07-07T15:12:26.1528717+08:00
 tags = []
 
-[[root_connections.sql_files]]
+[[root_connections.scripts]]
 id = '971d8529'
 db_conn_id = '04d789fe'
 name = '23查询'
@@ -180,7 +180,7 @@ tags = ['用户', '查询']
 last_modified = 2025-07-11T17:03:45.1638427+08:00
 created_time = 2025-07-10T17:03:19.7218315+08:00
 
-[[root_connections.sql_files]]
+[[root_connections.scripts]]
 id = 'fe02b820'
 db_conn_id = '04d789fe'
 name = '新查询_2025-07-22T07-21-13.sql'
@@ -189,7 +189,7 @@ tags = ['query']
 last_modified = 2025-07-22T15:21:13.547313+08:00
 created_time = 2025-07-22T15:21:13.547313+08:00
 
-[[root_connections.sql_files]]
+[[root_connections.scripts]]
 id = '85ba836f'
 db_conn_id = '04d789fe'
 name = '新查询_2025-08-07T03-46-15.sql'
@@ -198,7 +198,7 @@ tags = ['query']
 last_modified = 2025-08-07T11:46:15.8457901+08:00
 created_time = 2025-08-07T11:46:15.8457901+08:00
 
-[[root_connections.sql_files]]
+[[root_connections.scripts]]
 id = 'f121c4fd'
 db_conn_id = '04d789fe'
 name = '1'
@@ -211,8 +211,8 @@ created_time = 2025-08-21T17:23:48.7213165+08:00
 id = '072d1bdb'
 name = '本地'
 description = ''
-db_type = 'mysql'
-db_version = ''
+type = 'mysql'
+version = ''
 host = '127.0.0.1'
 port = 3306
 username = 'root'

+ 10 - 8
service/internal/modules/connection_pool/API_EXAMPLES.md

@@ -11,15 +11,17 @@ curl -X POST http://localhost:8080/api/v1/connection_pool/register \
   -H "Content-Type: application/json" \
   -d '{
     "conn_id": "mysql_primary",
-    "db_type": "mysql",
-    "db_version": "v8",
-    "config": {
+    "db_detail": {
+      "type": "mysql",
+      "version": "v8",
       "host": "127.0.0.1",
       "port": 3306,
       "username": "root",
       "password": "secret",
       "database": "demo",
-      "schema": "demo",
+      "schema": "demo"
+    },
+    "config": {
       "max_open_conns": 10,
       "max_idle_conns": 5,
       "max_idle_time_seconds": 300,
@@ -36,8 +38,8 @@ curl -X POST http://localhost:8080/api/v1/connection_pool/register \
   "msg": "注册连接成功",
   "data": {
     "conn_id": "mysql_primary@127-0-0-1",
-    "db_type": "mysql",
-    "db_version": "v8",
+    "type": "mysql",
+    "version": "v8",
     "last_used": "2024-03-12T10:21:33.456789+08:00",
     "connected": true,
     "max_idle_time_seconds": 300,
@@ -66,8 +68,8 @@ curl -X POST http://localhost:8080/api/v1/connection_pool/stats \
     "connections": [
       {
         "conn_id": "mysql_primary@127-0-0-1",
-        "db_type": "mysql",
-        "db_version": "v8",
+        "type": "mysql",
+        "version": "v8",
         "last_used": "2024-03-12T10:21:33.456789+08:00",
         "connected": true,
         "max_idle_time_seconds": 300,

+ 6 - 6
service/internal/modules/connection_pool/api/types.go

@@ -19,10 +19,10 @@ type ConnectionConfig struct {
 
 // RegisterConnectionRequest 注册连接请求
 type RegisterConnectionRequest struct {
-	ConnID    string            `json:"conn_id" binding:"required"`
-	DBType    string            `json:"db_type" binding:"required"`
-	DBVersion string            `json:"db_version"`
-	Config    *ConnectionConfig `json:"config" binding:"required"`
+	ConnID  string            `json:"conn_id" binding:"required"`
+	Type    string            `json:"type" binding:"required"`
+	Version string            `json:"version"`
+	Config  *ConnectionConfig `json:"config" binding:"required"`
 }
 
 // CloseConnectionRequest 关闭指定连接
@@ -43,8 +43,8 @@ type ConnectionStatsResponse struct {
 // ConnectionStats 单个连接的状态信息
 type ConnectionStats struct {
 	ConnID             string    `json:"conn_id"`
-	DBType             string    `json:"db_type"`
-	DBVersion          string    `json:"db_version"`
+	Type               string    `json:"type"`
+	Version            string    `json:"version"`
 	LastUsed           time.Time `json:"last_used"`
 	Connected          bool      `json:"connected"`
 	MaxIdleTimeSeconds int       `json:"max_idle_time_seconds"`

+ 7 - 7
service/internal/modules/connection_pool/service/connection_pool_service.go

@@ -34,8 +34,8 @@ func RegisterConnection(req *api.RegisterConnectionRequest) (*api.ConnectionStat
 	if req.ConnID == "" {
 		return nil, errors.New("conn_id 不能为空")
 	}
-	if req.DBType == "" {
-		return nil, errors.New("db_type 不能为空")
+	if req.Type == "" {
+		return nil, errors.New("type 不能为空")
 	}
 
 	config := &meta.ConnectionConfig{
@@ -56,12 +56,12 @@ func RegisterConnection(req *api.RegisterConnectionRequest) (*api.ConnectionStat
 		config.Schema = config.Database
 	}
 
-	dbVersion := req.DBVersion
+	dbVersion := req.Version
 	if dbVersion == "" {
-		dbVersion = req.DBType
+		dbVersion = req.Type
 	}
 
-	if err := connPool.RegisterConnectionWithConfig(req.ConnID, req.DBType, dbVersion, config); err != nil {
+	if err := connPool.RegisterConnectionWithConfig(req.ConnID, req.Type, dbVersion, config); err != nil {
 		return nil, err
 	}
 
@@ -150,8 +150,8 @@ func convertPoolStats(connID string, st connection.PoolStats) *api.ConnectionSta
 
 	return &api.ConnectionStats{
 		ConnID:             connID,
-		DBType:             st.DBType,
-		DBVersion:          st.DBVersion,
+		Type:               st.Type,
+		Version:            st.Version,
 		LastUsed:           st.LastUsed,
 		Connected:          st.IsConnected,
 		MaxIdleTimeSeconds: maxIdleTimeSeconds,

+ 2 - 2
service/internal/modules/data_query/service/service.go

@@ -191,12 +191,12 @@ func (s *DataService) executeQuery(ctx context.Context, connID string, connInfo
 			return meta.QueryResult{}, fmt.Errorf("从连接池获取连接失败: %w", err)
 		}
 		if pc == nil || pc.DbConn == nil {
-			return meta.QueryResult{}, fmt.Errorf("连接 %s 未就绪", connID)
+			return meta.QueryResult{}, fmt.Errorf("连接 %s 未就绪 , %s", connID, connInfo)
 		}
 		if reader, ok := pc.DbConn.(dbpkg.DataReader); ok {
 			return reader.QueryData(ctx, path, req)
 		}
-		return meta.QueryResult{}, fmt.Errorf("连接 %s 不支持 DataReader 接口", connID)
+		return meta.QueryResult{}, fmt.Errorf("连接 %s 不支持 DataReader 接口 , %s", connID, connInfo)
 	}
 	// 回退到注入的实现
 	return s.r.QueryData(ctx, path, req)

+ 41 - 0
service/internal/modules/database_meta/API.md

@@ -29,6 +29,7 @@ Database Metadata API 提供完整的数据库元数据管理功能,支持多
 | 创建对象 | `POST /database/metadata/object/create` | 创建数据库对象(支持预览/执行) |
 | 更新对象 | `POST /database/metadata/object/update` | 更新数据库对象(支持预览/执行) |
 | 删除对象 | `POST /database/metadata/object/delete` | 删除数据库对象(支持预览/执行) |
+| 获取元信息 | `POST /database/metadata/info` | 获取数据库支持的关键字、字段类型和能力信息(用于自动完成/语法高亮/能力探测) |
 | 刷新元数据 | `POST /database/metadata/object/refresh` | 刷新对象的元数据缓存 |
 
 ### 🏗️ 设计原则
@@ -317,6 +318,46 @@ FieldType 可用值
 
 ---
 
+## X) 获取元信息 — POST /database/metadata/info
+
+用途
+- 返回数据库引擎层面的元信息,供前端的自动完成、语法高亮、类型提示与能力检测使用。
+
+请求字段
+| 字段 | 类型 | 必需 | 说明 |
+|---|---:|:---:|---|
+| connId | string | 是 | 连接池查找键(必须) |
+| connInfo | string | 否 | 展示/日志用的连接备注 |
+
+示例请求
+```json
+{
+  "connId": "conn-123",
+  "connInfo": "mysql-prod-01"
+}
+```
+
+成功响应(示例)
+```json
+{
+  "code": 0,
+  "msg": "ok",
+  "data": {
+    "keywords": ["ADD","ALTER","CREATE","SELECT","WHERE"],
+    "fieldTypes": ["BIGINT","CHAR","DATE","DATETIME","DECIMAL","INT","VARCHAR"],
+    "typeAliases": {"string":["VARCHAR","TEXT"]},
+    "capabilities": {"supportsTransactions":true,"supportsDDLTransaction":false},
+    "functions": ["COUNT","SUM","NOW"]
+  }
+}
+```
+
+说明与注意
+- 返回的数据结构对应 `meta.MetadataCapabilities`:包含 `keywords`(关键字/保留字)、`fieldTypes`(支持的数据类型,已规范化为大写)、`typeAliases`(可选的类型别名映射)、`capabilities`(布尔能力标记)、`functions`(可选函数列表)以及 `extras`(驱动或实现相关的额外信息)。
+- 该接口优先从连接池中指定连接获取信息;若驱动未实现或权限不足,服务可能返回错误(HTTP 500/400),并在 `error` 字段给出详细原因。
+- 关键字/类型列表在不同 MySQL 版本间存在差异;客户端应以返回值为准并可做缓存。若需要更实时或更丰富的数据(例如函数签名),建议后端扩展该接口并加入缓存策略或版本信息。
+
+
 ## 关于“当前值 (current)” 的获取与使用
 
 为了让前端在修改/删除场景能够方便地渲染表单默认值或展示影响预览,服务端模板返回了两个层次的当前值:

+ 6 - 0
service/internal/modules/database_meta/api/api.go

@@ -93,3 +93,9 @@ type DeleteTemplateRequest struct {
 	ConnInfo string          `json:"connInfo"`
 	Path     meta.ObjectPath `json:"path" binding:"required"` // 目标对象的路径
 }
+
+// GetMetadataInfoRequest 用于 POST /database/metadata/info
+type GetMetadataInfoRequest struct {
+	ConnID   string `json:"connId" binding:"required"`
+	ConnInfo string `json:"connInfo"`
+}

+ 16 - 0
service/internal/modules/database_meta/handler/handler.go

@@ -206,3 +206,19 @@ func (h *Handler) DescribeDeleteTemplateHandler(c *gin.Context) {
 	}
 	c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: tpl})
 }
+
+// GetMetadataInfoHandler POST /database/metadata/info
+// body: api.GetMetadataInfoRequest
+func (h *Handler) GetMetadataInfoHandler(c *gin.Context) {
+	var req api.GetMetadataInfoRequest
+	if err := c.ShouldBindJSON(&req); err != nil {
+		c.JSON(http.StatusBadRequest, response.Response{Code: 1, Msg: "请求参数错误", Error: err.Error()})
+		return
+	}
+	caps, err := h.svc.GetMetadataInfo(c.Request.Context(), req.ConnID)
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, response.Response{Code: 2, Msg: "获取元信息失败", Error: err.Error()})
+		return
+	}
+	c.JSON(http.StatusOK, response.Response{Code: 0, Msg: "ok", Data: caps})
+}

+ 1 - 0
service/internal/modules/database_meta/route.go

@@ -25,6 +25,7 @@ func RegisterRoutes(r *gin.Engine, svcObj *svc.MetadataService) {
 		grp.POST("/object/update", handler.UpdateObjectHandler)
 		grp.POST("/delete/template", handler.DescribeDeleteTemplateHandler)
 
+		grp.POST("/info", handler.GetMetadataInfoHandler)
 		grp.POST("/object/delete", handler.DeleteObjectsHandler)
 	}
 }

+ 26 - 0
service/internal/modules/database_meta/service/service.go

@@ -194,3 +194,29 @@ func (s *MetadataService) DeleteObjects(ctx context.Context, req meta.ObjectOper
 	}
 	return s.d.DeleteChildObjects(ctx, req)
 }
+
+// GetMetadataInfo 从可用连接或注入实现获取元信息(关键字、字段类型等)
+func (s *MetadataService) GetMetadataInfo(ctx context.Context, connID string) (meta.MetadataCapabilities, error) {
+	// 优先使用连接池查找
+	if s.Pool != nil {
+		pc, err := s.Pool.GetConnection(connID)
+		if err != nil {
+			return meta.MetadataCapabilities{}, fmt.Errorf("从连接池获取连接失败: %w", err)
+		}
+		if pc == nil || pc.DbConn == nil {
+			return meta.MetadataCapabilities{}, fmt.Errorf("连接 %s 未就绪", connID)
+		}
+		if info, ok := pc.DbConn.(dbpkg.MetadataInfo); ok {
+			return info.GetMetadataInfo(ctx)
+		}
+		return meta.MetadataCapabilities{}, fmt.Errorf("连接 %s 不支持 MetadataInfo 接口", connID)
+	}
+
+	// 回退:尝试从已注入的 reader 实现中获取
+	if s.r != nil {
+		if info, ok := s.r.(dbpkg.MetadataInfo); ok {
+			return info.GetMetadataInfo(ctx)
+		}
+	}
+	return meta.MetadataCapabilities{}, fmt.Errorf("MetadataInfo 未实现或服务未配置连接池")
+}

+ 108 - 84
service/internal/modules/db_conn_storage/API_EXAMPLES.md

@@ -26,18 +26,14 @@
 #### 创建数据库连接
 
 **参数说明**:
-- `group_id` (string, 必需): 数据库连接所属的分组ID(系统会验证此ID是否存在)
-- `name` (string, 必需): 数据库连接的名称,用于标识连接
-- `description` (string, 可选): 数据库连接的详细描述
-- `db_type` (string, 必需): 数据库类型,支持: mysql, postgresql, oracle, sqlserver, sqlite等
-- `db_version` (string, 必需): 数据库版本号,如 "8.0", "13.0"等
-- `server` (string, 必需): 数据库服务器主机地址或IP
-- `port` (integer, 必需): 数据库服务器端口号
-- `username` (string, 必需): 数据库连接用户名
-- `password` (string, 必需): 数据库连接密码(可为明文或客户端加密/编码后的字符串;服务端会根据实现进行解码/解密)
-- `database` (string, 必需): 要连接的数据库名称
+- `group_id` (string, 必需): 连接所属的分组ID(系统会验证此ID是否存在)
+- `name` (string, 必需): 连接的名称,用于标识连接
+- `description` (string, 可选): 连接的详细描述
+- `kind` (string, 必需): 连接类别,示例值为 `database` / `server` / `other`。根据 `kind` 的值,请在请求体中提供对应的嵌套对象:
+  - 当 `kind=="database"` 时,请使用 `db_detail` 对象(示例见下)。
+  - 当 `kind=="server"` 时,请使用 `server_detail` 对象(示例见下)。
 - `color` (string, 可选): 连接在UI中显示的颜色
-- `auto_connect` (boolean, 可选): 是否自动连接,默认为false
+- `auto_connect` (boolean, 可选): 是否自动连接,默认为 false
 - `display_order` (int, 可选): 显示顺序,如果未提供或为0,系统会自动计算为同分组连接最大显示顺序 + 1
 
 **验证逻辑**:
@@ -52,15 +48,19 @@ curl -X POST http://localhost:8080/api/v1/storage/connections/create \
     "group_id": "group_1",
     "name": "MySQL Production DB",
     "description": "生产环境主数据库",
-    "db_type": "mysql",
-    "db_version": "8.0",
-    "server": "prod-db.example.com",
-    "port": 3306,
-    "username": "app_user",
-  "password": "enc:secure_password",
-    "database": "myapp_prod",
+    "kind": "database",
     "color": "#4285F4",
-    "auto_connect": false
+    "auto_connect": false,
+    "db_detail": {
+      "type": "mysql",
+      "version": "8.0",
+      "server": "prod-db.example.com",
+      "port": 3306,
+      "username": "app_user",
+      "password": "enc:secure_password",
+      "database": "myapp_prod",
+      "use_ssh_tunnel": false
+    }
   }'
 ```
 
@@ -74,12 +74,16 @@ curl -X POST http://localhost:8080/api/v1/storage/connections/create \
     "group_id": "group_1",
     "name": "MySQL Production DB",
     "description": "生产环境主数据库",
-    "db_type": "mysql",
-    "db_version": "8.0",
-    "server": "prod-db.example.com",
-    "port": 3306,
-    "username": "app_user",
-    "database": "myapp_prod",
+    "kind": "database",
+    "db_detail": {
+      "type": "mysql",
+      "version": "8.0",
+      "server": "prod-db.example.com",
+      "port": 3306,
+      "username": "app_user",
+      "database": "myapp_prod",
+      "last_connected": null
+    },
     "created_at": "2024-01-15T10:30:00Z",
     "updated_at": "2024-01-15T10:30:00Z"
   }
@@ -108,13 +112,15 @@ curl -X POST http://localhost:8080/api/v1/storage/connections/list \
       "group_id": "group_1",
       "name": "MySQL Production DB",
       "description": "生产环境主数据库",
-      "db_type": "mysql",
-      "db_version": "8.0",
-      "server": "prod-db.example.com",
-      "port": 3306,
-      "username": "app_user",
-      "database": "myapp_prod",
-      "last_connected": "2024-01-15T09:15:00Z",
+      "db_detail": {
+        "type": "mysql",
+        "version": "8.0",
+        "server": "prod-db.example.com",
+        "port": 3306,
+        "username": "app_user",
+        "database": "myapp_prod",
+        "last_connected": "2024-01-15T09:15:00Z"
+      },
       "created_at": "2024-01-15T10:30:00Z",
       "updated_at": "2024-01-15T10:30:00Z"
     },
@@ -123,13 +129,15 @@ curl -X POST http://localhost:8080/api/v1/storage/connections/list \
       "group_id": "group_2",
       "name": "PostgreSQL Development DB",
       "description": "开发环境数据库",
-      "db_type": "postgresql",
-      "db_version": "13.0",
-      "server": "dev-db.example.com",
-      "port": 5432,
-      "username": "dev_user",
-      "database": "myapp_dev",
-      "last_connected": "2024-01-14T16:30:00Z",
+      "db_detail": {
+        "type": "postgresql",
+        "version": "13.0",
+        "server": "dev-db.example.com",
+        "port": 5432,
+        "username": "dev_user",
+        "database": "myapp_dev",
+        "last_connected": "2024-01-14T16:30:00Z"
+      },
       "created_at": "2024-01-14T08:45:00Z",
       "updated_at": "2024-01-14T08:45:00Z"
     }
@@ -160,13 +168,15 @@ curl -X POST http://localhost:8080/api/v1/storage/connections/get \
     "group_id": "group_1",
     "name": "MySQL Production DB",
     "description": "生产环境主数据库",
-    "db_type": "mysql",
-    "db_version": "8.0",
-    "server": "prod-db.example.com",
-    "port": 3306,
-    "username": "app_user",
-    "database": "myapp_prod",
-    "last_connected": "2024-01-15T09:15:00Z",
+    "db_detail": {
+      "type": "mysql",
+      "version": "8.0",
+      "server": "prod-db.example.com",
+      "port": 3306,
+      "username": "app_user",
+      "database": "myapp_prod",
+      "last_connected": "2024-01-15T09:15:00Z"
+    },
     "auto_connect": false,
     "color": "#4285F4",
     "created_at": "2024-01-15T10:30:00Z",
@@ -194,8 +204,10 @@ curl -X POST http://localhost:8080/api/v1/storage/connections/update \
     "id": "conn_123456",
     "name": "MySQL Production DB (Updated)",
     "description": "生产环境主数据库 - 已更新配置",
-    "server": "new-prod-db.example.com",
-  "password": "enc:new_secure_password"
+    "db_detail": {
+      "server": "new-prod-db.example.com",
+      "password": "enc:new_secure_password"
+    }
   }'
 ```
 
@@ -209,13 +221,15 @@ curl -X POST http://localhost:8080/api/v1/storage/connections/update \
     "group_id": "group_1",
     "name": "MySQL Production DB (Updated)",
     "description": "生产环境主数据库 - 已更新配置",
-    "db_type": "mysql",
-    "db_version": "8.0",
-    "server": "new-prod-db.example.com",
-    "port": 3306,
-    "username": "app_user",
-    "database": "myapp_prod",
-    "last_connected": "2024-01-15T09:15:00Z",
+    "db_detail": {
+      "type": "mysql",
+      "version": "8.0",
+      "server": "new-prod-db.example.com",
+      "port": 3306,
+      "username": "app_user",
+      "database": "myapp_prod",
+      "last_connected": "2024-01-15T09:15:00Z"
+    },
     "created_at": "2024-01-15T10:30:00Z",
     "updated_at": "2024-01-15T11:45:00Z"
   }
@@ -269,13 +283,15 @@ curl -X POST http://localhost:8080/api/v1/storage/connections/move \
     "group_id": "group_3",
     "name": "MySQL Production DB",
     "description": "生产环境主数据库",
-    "db_type": "mysql",
-    "db_version": "8.0",
-    "server": "prod-db.example.com",
-    "port": 3306,
-    "username": "app_user",
-    "database": "myapp_prod",
-    "last_connected": "2024-01-15T09:15:00Z",
+    "db_detail": {
+      "type": "mysql",
+      "version": "8.0",
+      "server": "prod-db.example.com",
+      "port": 3306,
+      "username": "app_user",
+      "database": "myapp_prod",
+      "last_connected": "2024-01-15T09:15:00Z"
+    },
     "created_at": "2024-01-15T10:30:00Z",
     "updated_at": "2024-01-15T12:00:00Z"
   }
@@ -386,8 +402,10 @@ curl -X POST http://localhost:8080/api/v1/storage/connection_groups/tree \
           {
             "id": "conn_123456",
             "name": "MySQL Production DB",
-            "db_type": "mysql",
-            "server": "prod-db.example.com"
+            "db_detail": {
+              "type": "mysql",
+              "server": "prod-db.example.com"
+            }
           }
         ],
         "children": []
@@ -403,8 +421,10 @@ curl -X POST http://localhost:8080/api/v1/storage/connection_groups/tree \
           {
             "id": "conn_789012",
             "name": "PostgreSQL Development DB",
-            "db_type": "postgresql",
-            "server": "dev-db.example.com"
+            "db_detail": {
+              "type": "postgresql",
+              "server": "dev-db.example.com"
+            }
           }
         ],
         "children": []
@@ -439,15 +459,17 @@ curl -X POST http://localhost:8080/api/v1/storage/connection_groups/get \
     "description": "生产环境数据库连接",
     "icon": "server",
     "display_order": 1,
-    "connections": [
+      "connections": [
       {
         "id": "conn_123456",
         "name": "MySQL Production DB",
-        "db_type": "mysql",
-        "server": "prod-db.example.com",
-        "port": 3306,
-        "username": "app_user",
-        "database": "myapp_prod"
+        "db_detail": {
+          "type": "mysql",
+          "server": "prod-db.example.com",
+          "port": 3306,
+          "username": "app_user",
+          "database": "myapp_prod"
+        }
       }
     ],
     "children": [],
@@ -1050,19 +1072,21 @@ curl -X POST http://localhost:8080/api/v1/storage/script_groups/move \
   "group_id": "string",
   "name": "string",
   "description": "string",
-  "db_type": "mysql|postgresql|oracle|sqlserver",
-  "db_version": "string",
-  "server": "string",
-  "port": "integer",
-  "username": "string",
-  "database": "string",
-  "connection_string": "string",
-  "use_ssh_tunnel": "boolean",
+  "db_detail": {
+    "type": "mysql|postgresql|oracle|sqlserver",
+    "version": "string",
+    "server": "string",
+    "port": "integer",
+    "username": "string",
+    "database": "string",
+    "connection_string": "string",
+    "use_ssh_tunnel": "boolean",
+    "last_connected": "datetime"
+  },
   "color": "string",
-  "last_connected": "datetime",
   "auto_connect": "boolean",
   "display_order": "integer",
-  "scripts": ["SQLScriptResponse"],
+  "scripts": ["ScriptResponse"],
   "created_at": "datetime",
   "updated_at": "datetime"
 }
@@ -1084,7 +1108,7 @@ curl -X POST http://localhost:8080/api/v1/storage/script_groups/move \
 }
 ```
 
-### SQLScriptResponse
+### ScriptResponse
 ```json
 {
   "id": "string",
@@ -1108,7 +1132,7 @@ curl -X POST http://localhost:8080/api/v1/storage/script_groups/move \
   "parent_id": "string",
   "name": "string",
   "description": "string",
-  "scripts": ["SQLScriptResponse"],
+  "scripts": ["ScriptResponse"],
   "children": ["ScriptGroupResponse"],
   "created_at": "datetime",
   "updated_at": "datetime"

+ 160 - 94
service/internal/modules/db_conn_storage/api/types.go

@@ -73,7 +73,7 @@ type ScriptGroupResponse struct {
 	ParentID    string                 `json:"parent_id"`
 	Name        string                 `json:"name"`
 	Description string                 `json:"description"`
-	Scripts     []*SQLScriptResponse   `json:"scripts,omitempty"`
+	Scripts     []*ScriptResponse      `json:"scripts,omitempty"`
 	Children    []*ScriptGroupResponse `json:"children,omitempty"`
 	CreatedAt   time.Time              `json:"created_at"`
 	UpdatedAt   time.Time              `json:"updated_at"`
@@ -122,41 +122,30 @@ type GetScriptGroupTreeRequest struct {
 
 // CreateConnectionRequest 创建连接请求
 type CreateConnectionRequest struct {
-	GroupID          string `json:"group_id" binding:"required"`
-	Name             string `json:"name" binding:"required"`
-	Description      string `json:"description"`
-	DBType           string `json:"db_type" binding:"required"`
-	DBVersion        string `json:"db_version" binding:"required"`
-	Server           string `json:"server" binding:"required"`
-	Port             int    `json:"port" binding:"required"`
-	Username         string `json:"username"`
-	Password         string `json:"password"`
-	Database         string `json:"database"`
-	ConnectionString string `json:"connection_string"`
-	UseSSHTunnel     bool   `json:"use_ssh_tunnel"`
-	Color            string `json:"color"`
-	AutoConnect      bool   `json:"auto_connect"`
-	DisplayOrder     int    `json:"display_order"`
+	GroupID      string        `json:"group_id" binding:"required"`
+	Name         string        `json:"name" binding:"required"`
+	Description  string        `json:"description"`
+	Kind         string        `json:"kind" binding:"required"` // "database" | "server" | "other"
+	DBDetail     *DBDetail     `json:"db_detail,omitempty"`     // 当 kind=="database" 时使用
+	ServerDetail *ServerDetail `json:"server_detail,omitempty"` // 当 kind=="server" 时使用
+	Color        string        `json:"color"`
+	AutoConnect  bool          `json:"auto_connect"`
+	DisplayOrder int           `json:"display_order"`
 }
 
 // UpdateConnectionRequest 更新连接请求
 type UpdateConnectionRequest struct {
-	ID               string  `json:"id" binding:"required"`
-	GroupID          *string `json:"group_id,omitempty"`
-	Name             *string `json:"name,omitempty"`
-	Description      *string `json:"description,omitempty"`
-	DBType           *string `json:"db_type,omitempty"`
-	DBVersion        *string `json:"db_version,omitempty"`
-	Server           *string `json:"server,omitempty"`
-	Port             *int    `json:"port,omitempty"`
-	Username         *string `json:"username,omitempty"`
-	Password         *string `json:"password,omitempty"`
-	Database         *string `json:"database,omitempty"`
-	ConnectionString *string `json:"connection_string,omitempty"`
-	UseSSHTunnel     *bool   `json:"use_ssh_tunnel,omitempty"`
-	Color            *string `json:"color,omitempty"`
-	AutoConnect      *bool   `json:"auto_connect,omitempty"`
-	DisplayOrder     *int    `json:"display_order,omitempty"`
+	ID           string              `json:"id" binding:"required"`
+	GroupID      *string             `json:"group_id,omitempty"`
+	Name         *string             `json:"name,omitempty"`
+	Description  *string             `json:"description,omitempty"`
+	Type         *string             `json:"-"`
+	Version      *string             `json:"-"`
+	DBDetail     *DBDetailUpdate     `json:"db_detail,omitempty"`
+	ServerDetail *ServerDetailUpdate `json:"server_detail,omitempty"`
+	Color        *string             `json:"color,omitempty"`
+	AutoConnect  *bool               `json:"auto_connect,omitempty"`
+	DisplayOrder *int                `json:"display_order,omitempty"`
 }
 
 // MoveConnectionRequest 移动连接请求
@@ -178,85 +167,162 @@ type DeleteConnectionRequest struct {
 
 // DBConnectionResponse 数据库连接响应
 type DBConnectionResponse struct {
-	ID               string               `json:"id"`
-	GroupID          string               `json:"group_id"`
-	Name             string               `json:"name"`
-	Description      string               `json:"description"`
-	DBType           string               `json:"db_type"`
-	DBVersion        string               `json:"db_version"`
-	Server           string               `json:"server"`
-	Port             int                  `json:"port"`
-	Username         string               `json:"username"`
-	Password         string               `json:"password"`
-	Database         string               `json:"database"`
-	ConnectionString string               `json:"connection_string"`
-	UseSSHTunnel     bool                 `json:"use_ssh_tunnel"`
-	Color            string               `json:"color"`
-	LastConnected    time.Time            `json:"last_connected"`
-	AutoConnect      bool                 `json:"auto_connect"`
-	DisplayOrder     int                  `json:"display_order"`
-	Scripts          []*SQLScriptResponse `json:"scripts,omitempty"`
-	CreatedAt        time.Time            `json:"created_at"`
-	UpdatedAt        time.Time            `json:"updated_at"`
+	ID            string            `json:"id"`
+	GroupID       string            `json:"group_id"`
+	Name          string            `json:"name"`
+	Description   string            `json:"description"`
+	Kind          string            `json:"kind"`
+	DBDetail      *DBDetail         `json:"db_detail,omitempty"`
+	ServerDetail  *ServerDetail     `json:"server_detail,omitempty"`
+	Color         string            `json:"color"`
+	LastConnected time.Time         `json:"last_connected"`
+	AutoConnect   bool              `json:"auto_connect"`
+	DisplayOrder  int               `json:"display_order"`
+	Scripts       []*ScriptResponse `json:"scripts,omitempty"`
+	CreatedAt     time.Time         `json:"created_at"`
+	UpdatedAt     time.Time         `json:"updated_at"`
+}
+
+// ConnectionDetailsResponse 新的连接详情响应(替代旧的 DBConnectionResponse 用于 /storage/connections/get)
+type ConnectionDetailsResponse struct {
+	ID            string            `json:"id"`
+	GroupID       string            `json:"group_id"`
+	Name          string            `json:"name"`
+	Description   string            `json:"description"`
+	Kind          string            `json:"kind"`
+	DBDetail      *DBDetail         `json:"db_detail,omitempty"`
+	ServerDetail  *ServerDetail     `json:"server_detail,omitempty"`
+	Color         string            `json:"color"`
+	LastConnected time.Time         `json:"last_connected"`
+	AutoConnect   bool              `json:"auto_connect"`
+	DisplayOrder  int               `json:"display_order"`
+	Scripts       []*ScriptResponse `json:"scripts,omitempty"`
+	CreatedAt     time.Time         `json:"created_at"`
+	UpdatedAt     time.Time         `json:"updated_at"`
+}
+
+// DBDetail 嵌套对象,表示数据库连接的细节
+type DBDetail struct {
+	Type             string `json:"type"`              // mysql/postgres 等
+	Version          string `json:"version,omitempty"` // 可选
+	Server           string `json:"server"`            // host
+	Port             int    `json:"port"`
+	Username         string `json:"username,omitempty"`
+	Password         string `json:"password,omitempty"`
+	Database         string `json:"database,omitempty"`
+	ConnectionString string `json:"connection_string,omitempty"`
+	UseSSHTunnel     bool   `json:"use_ssh_tunnel,omitempty"`
+}
+
+// 可用于更新时的可选字段版本
+type DBDetailUpdate struct {
+	Type             *string `json:"type,omitempty"`
+	Version          *string `json:"version,omitempty"`
+	Server           *string `json:"server,omitempty"`
+	Port             *int    `json:"port,omitempty"`
+	Username         *string `json:"username,omitempty"`
+	Password         *string `json:"password,omitempty"`
+	Database         *string `json:"database,omitempty"`
+	ConnectionString *string `json:"connection_string,omitempty"`
+	UseSSHTunnel     *bool   `json:"use_ssh_tunnel,omitempty"`
+}
+
+// ServerDetail 嵌套对象,表示服务器/SSH等连接细节
+type ServerDetail struct {
+	Type       string `json:"type"` // e.g., "ssh"
+	Version    string `json:"version,omitempty"`
+	Server     string `json:"server"`
+	Port       int    `json:"port"`
+	Username   string `json:"username,omitempty"`
+	AuthType   string `json:"auth_type,omitempty"`
+	PrivateKey string `json:"private_key,omitempty"`
+	UseSudo    bool   `json:"use_sudo,omitempty"`
+}
+
+type ServerDetailUpdate struct {
+	Type       *string `json:"type,omitempty"`
+	Version    *string `json:"version,omitempty"`
+	Server     *string `json:"server,omitempty"`
+	Port       *int    `json:"port,omitempty"`
+	Username   *string `json:"username,omitempty"`
+	AuthType   *string `json:"auth_type,omitempty"`
+	PrivateKey *string `json:"private_key,omitempty"`
+	UseSudo    *bool   `json:"use_sudo,omitempty"`
 }
 
 // SQL脚本相关请求和响应
 
-// CreateSQLScriptRequest 创建SQL脚本请求
-type CreateSQLScriptRequest struct {
-	ConnectionID string `json:"connection_id" binding:"required"`
-	GroupID      string `json:"group_id" binding:"required"`
-	Name         string `json:"name" binding:"required"`
-	Description  string `json:"description"`
-	Content      string `json:"content"`
-	Favorite     bool   `json:"favorite"`
+// CreateScriptRequest 创建脚本请求
+type CreateScriptRequest struct {
+	ConnectionID string            `json:"connection_id" binding:"required"`
+	GroupID      string            `json:"group_id" binding:"required"`
+	Name         string            `json:"name" binding:"required"`
+	Description  string            `json:"description"`
+	Content      string            `json:"content"`
+	Favorite     bool              `json:"favorite"`
+	Language     string            `json:"language"`
+	Metadata     map[string]string `json:"metadata,omitempty"`
+	Tags         []string          `json:"tags,omitempty"`
+	Enabled      bool              `json:"enabled"`
+	Owner        string            `json:"owner"`
 }
 
-// UpdateSQLScriptRequest 更新SQL脚本请求
-type UpdateSQLScriptRequest struct {
-	ID          string `json:"id" binding:"required"`
-	Name        string `json:"name"`
-	Description string `json:"description"`
-	Content     string `json:"content"`
-	Favorite    bool   `json:"favorite"`
-}
-
-// SQLScriptResponse SQL脚本响应
-type SQLScriptResponse struct {
-	ID             string    `json:"id"`
-	ConnectionID   string    `json:"connection_id"`
-	GroupID        string    `json:"group_id"`
-	Name           string    `json:"name"`
-	Description    string    `json:"description"`
-	Content        string    `json:"content"`
-	Favorite       bool      `json:"favorite"`
-	LastExecuted   time.Time `json:"last_executed"`
-	ExecutionCount int       `json:"execution_count"`
-	CreatedAt      time.Time `json:"created_at"`
-	UpdatedAt      time.Time `json:"updated_at"`
-}
-
-// MoveSQLScriptRequest 移动SQL脚本请求
-type MoveSQLScriptRequest struct {
+// UpdateScriptRequest 更新脚本请求
+type UpdateScriptRequest struct {
+	ID          string             `json:"id" binding:"required"`
+	Name        *string            `json:"name,omitempty"`
+	Description *string            `json:"description,omitempty"`
+	Content     *string            `json:"content,omitempty"`
+	Favorite    *bool              `json:"favorite,omitempty"`
+	Language    *string            `json:"language,omitempty"`
+	Metadata    *map[string]string `json:"metadata,omitempty"`
+	Tags        *[]string          `json:"tags,omitempty"`
+	Enabled     *bool              `json:"enabled,omitempty"`
+	Owner       *string            `json:"owner,omitempty"`
+}
+
+// ScriptResponse 脚本响应
+type ScriptResponse struct {
+	ID             string            `json:"id"`
+	ConnectionID   string            `json:"connection_id"`
+	GroupID        string            `json:"group_id"`
+	Name           string            `json:"name"`
+	Description    string            `json:"description"`
+	Content        string            `json:"content"`
+	Favorite       bool              `json:"favorite"`
+	Language       string            `json:"language"`
+	Metadata       map[string]string `json:"metadata,omitempty"`
+	Tags           []string          `json:"tags,omitempty"`
+	Enabled        bool              `json:"enabled"`
+	Owner          string            `json:"owner"`
+	LastExecuted   time.Time         `json:"last_executed"`
+	LastRunStatus  string            `json:"last_run_status"`
+	ExecutionCount int               `json:"execution_count"`
+	CreatedAt      time.Time         `json:"created_at"`
+	UpdatedAt      time.Time         `json:"updated_at"`
+}
+
+// MoveScriptRequest 移动脚本请求
+type MoveScriptRequest struct {
 	TargetGroupID string `json:"target_group_id" binding:"required"`
 }
 
-// GetSQLScriptRequest 获取SQL脚本请求
-type GetSQLScriptRequest struct {
+// GetScriptRequest 获取脚本请求
+type GetScriptRequest struct {
 	ID string `json:"id" binding:"required"`
 }
 
-// DeleteSQLScriptRequest 删除SQL脚本请求
-type DeleteSQLScriptRequest struct {
+// DeleteScriptRequest 删除脚本请求
+type DeleteScriptRequest struct {
 	ID string `json:"id" binding:"required"`
 }
 
-// ListSQLScriptsRequest 获取指定连接的SQL脚本列表请求
-type ListSQLScriptsRequest struct {
+// ListScriptsRequest 获取指定连接的脚本列表请求
+type ListScriptsRequest struct {
 	ConnectionID string `json:"connection_id" binding:"required"`
 }
 
-// UpdateSQLScriptExecutionStatsRequest 更新SQL脚本执行统计请求
-type UpdateSQLScriptExecutionStatsRequest struct {
+// UpdateScriptExecutionStatsRequest 更新脚本执行统计请求
+type UpdateScriptExecutionStatsRequest struct {
 	ID string `json:"id" binding:"required"`
 }

+ 55 - 31
service/internal/modules/db_conn_storage/handler/storage_handler.go

@@ -189,7 +189,31 @@ func GetConnection(c *gin.Context) {
 		return
 	}
 
-	c.JSON(http.StatusOK, response.Success(conn))
+	// 将旧的 DBConnectionResponse 映射为新的 ConnectionDetailsResponse,按 Kind 返回嵌套 detail
+	var out api.ConnectionDetailsResponse
+	if conn != nil {
+		out.ID = conn.ID
+		out.GroupID = conn.GroupID
+		out.Name = conn.Name
+		out.Description = conn.Description
+		out.Kind = conn.Kind
+		out.Color = conn.Color
+		out.LastConnected = conn.LastConnected
+		out.AutoConnect = conn.AutoConnect
+		out.DisplayOrder = conn.DisplayOrder
+		out.Scripts = conn.Scripts
+		out.CreatedAt = conn.CreatedAt
+		out.UpdatedAt = conn.UpdatedAt
+
+		// 仅根据 Kind 填充对应嵌套 detail
+		if conn.Kind == "database" && conn.DBDetail != nil {
+			out.DBDetail = conn.DBDetail
+		} else if conn.Kind == "server" && conn.ServerDetail != nil {
+			out.ServerDetail = conn.ServerDetail
+		}
+	}
+
+	c.JSON(http.StatusOK, response.Success(out))
 }
 
 // UpdateConnection 更新连接
@@ -409,26 +433,26 @@ func GetScriptGroupTree(c *gin.Context) {
 
 // SQL脚本处理器方法
 
-// CreateSQLScript 创建SQL脚本
-func CreateSQLScript(c *gin.Context) {
-	var req api.CreateSQLScriptRequest
+// CreateScript 创建脚本
+func CreateScript(c *gin.Context) {
+	var req api.CreateScriptRequest
 	if err := c.ShouldBindJSON(&req); err != nil {
 		c.JSON(http.StatusBadRequest, response.ParamErrorWithDetail("请求参数错误", err))
 		return
 	}
 
-	script, err := service.CreateSQLScript(&req)
+	script, err := service.CreateScript(&req)
 	if err != nil {
-		c.JSON(http.StatusInternalServerError, response.DBErrorWithDetail("创建SQL脚本失败", err))
+		c.JSON(http.StatusInternalServerError, response.DBErrorWithDetail("创建脚本失败", err))
 		return
 	}
 
 	c.JSON(http.StatusOK, response.Success(script))
 }
 
-// GetSQLScript 获取SQL脚本详情
-func GetSQLScript(c *gin.Context) {
-	var req api.GetSQLScriptRequest
+// GetScript 获取脚本详情
+func GetScript(c *gin.Context) {
+	var req api.GetScriptRequest
 	if err := c.ShouldBindJSON(&req); err != nil {
 		c.JSON(http.StatusBadRequest, response.ParamErrorWithDetail("请求参数错误", err))
 		return
@@ -439,18 +463,18 @@ func GetSQLScript(c *gin.Context) {
 		return
 	}
 
-	script, err := service.GetSQLScript(req.ID)
+	script, err := service.GetScript(req.ID)
 	if err != nil {
-		c.JSON(http.StatusInternalServerError, response.DBErrorWithDetail("获取SQL脚本失败", err))
+		c.JSON(http.StatusInternalServerError, response.DBErrorWithDetail("获取脚本失败", err))
 		return
 	}
 
 	c.JSON(http.StatusOK, response.Success(script))
 }
 
-// UpdateSQLScript 更新SQL脚本
-func UpdateSQLScript(c *gin.Context) {
-	var req api.UpdateSQLScriptRequest
+// UpdateScript 更新脚本
+func UpdateScript(c *gin.Context) {
+	var req api.UpdateScriptRequest
 	if err := c.ShouldBindJSON(&req); err != nil {
 		c.JSON(http.StatusBadRequest, response.ParamErrorWithDetail("请求参数错误", err))
 		return
@@ -461,18 +485,18 @@ func UpdateSQLScript(c *gin.Context) {
 		return
 	}
 
-	script, err := service.UpdateSQLScript(req.ID, &req)
+	script, err := service.UpdateScript(req.ID, &req)
 	if err != nil {
-		c.JSON(http.StatusInternalServerError, response.DBErrorWithDetail("更新SQL脚本失败", err))
+		c.JSON(http.StatusInternalServerError, response.DBErrorWithDetail("更新脚本失败", err))
 		return
 	}
 
 	c.JSON(http.StatusOK, response.Success(script))
 }
 
-// DeleteSQLScript 删除SQL脚本
-func DeleteSQLScript(c *gin.Context) {
-	var req api.DeleteSQLScriptRequest
+// DeleteScript 删除脚本
+func DeleteScript(c *gin.Context) {
+	var req api.DeleteScriptRequest
 	if err := c.ShouldBindJSON(&req); err != nil {
 		c.JSON(http.StatusBadRequest, response.ParamErrorWithDetail("请求参数错误", err))
 		return
@@ -483,18 +507,18 @@ func DeleteSQLScript(c *gin.Context) {
 		return
 	}
 
-	err := service.DeleteSQLScript(req.ID)
+	err := service.DeleteScript(req.ID)
 	if err != nil {
-		c.JSON(http.StatusInternalServerError, response.DBErrorWithDetail("删除SQL脚本失败", err))
+		c.JSON(http.StatusInternalServerError, response.DBErrorWithDetail("删除脚本失败", err))
 		return
 	}
 
 	c.JSON(http.StatusOK, response.Success(nil))
 }
 
-// ListSQLScripts 获取指定连接的所有SQL脚本
-func ListSQLScripts(c *gin.Context) {
-	var req api.ListSQLScriptsRequest
+// ListScripts 获取指定连接的所有脚本
+func ListScripts(c *gin.Context) {
+	var req api.ListScriptsRequest
 	if err := c.ShouldBindJSON(&req); err != nil {
 		c.JSON(http.StatusBadRequest, response.ParamErrorWithDetail("请求参数错误", err))
 		return
@@ -505,18 +529,18 @@ func ListSQLScripts(c *gin.Context) {
 		return
 	}
 
-	scripts, err := service.ListSQLScripts(req.ConnectionID)
+	scripts, err := service.ListScripts(req.ConnectionID)
 	if err != nil {
-		c.JSON(http.StatusInternalServerError, response.DBErrorWithDetail("获取SQL脚本列表失败", err))
+		c.JSON(http.StatusInternalServerError, response.DBErrorWithDetail("获取脚本列表失败", err))
 		return
 	}
 
 	c.JSON(http.StatusOK, response.Success(scripts))
 }
 
-// UpdateSQLScriptExecutionStats 更新SQL脚本执行统计
-func UpdateSQLScriptExecutionStats(c *gin.Context) {
-	var req api.UpdateSQLScriptExecutionStatsRequest
+// UpdateScriptExecutionStats 更新脚本执行统计
+func UpdateScriptExecutionStats(c *gin.Context) {
+	var req api.UpdateScriptExecutionStatsRequest
 	if err := c.ShouldBindJSON(&req); err != nil {
 		c.JSON(http.StatusBadRequest, response.ParamErrorWithDetail("请求参数错误", err))
 		return
@@ -527,9 +551,9 @@ func UpdateSQLScriptExecutionStats(c *gin.Context) {
 		return
 	}
 
-	err := service.UpdateSQLScriptExecutionStats(req.ID)
+	err := service.UpdateScriptExecutionStats(req.ID)
 	if err != nil {
-		c.JSON(http.StatusInternalServerError, response.DBErrorWithDetail("更新SQL脚本执行统计失败", err))
+		c.JSON(http.StatusInternalServerError, response.DBErrorWithDetail("更新脚本执行统计失败", err))
 		return
 	}
 

+ 6 - 6
service/internal/modules/db_conn_storage/routes.go

@@ -47,12 +47,12 @@ func RegisterRoutes(router *gin.Engine) {
 		// SQL脚本管理路由
 		scripts := v1.Group("/scripts")
 		{
-			scripts.POST("/create", handler.CreateSQLScript)                     // 创建SQL脚本
-			scripts.POST("/get", handler.GetSQLScript)                           // 获取指定SQL脚本
-			scripts.POST("/update", handler.UpdateSQLScript)                     // 更新SQL脚本
-			scripts.POST("/delete", handler.DeleteSQLScript)                     // 删除SQL脚本
-			scripts.POST("/list_by_connection", handler.ListSQLScripts)          // 获取指定连接的所有SQL脚本
-			scripts.POST("/update_stats", handler.UpdateSQLScriptExecutionStats) // 更新SQL脚本执行统计
+			scripts.POST("/create", handler.CreateScript)                     // 创建脚本
+			scripts.POST("/get", handler.GetScript)                           // 获取指定脚本
+			scripts.POST("/update", handler.UpdateScript)                     // 更新脚本
+			scripts.POST("/delete", handler.DeleteScript)                     // 删除脚本
+			scripts.POST("/list_by_connection", handler.ListScripts)          // 获取指定连接的所有脚本
+			scripts.POST("/update_stats", handler.UpdateScriptExecutionStats) // 更新脚本执行统计
 		}
 	}
 }

+ 229 - 96
service/internal/modules/db_conn_storage/service/storage_service.go

@@ -2,6 +2,7 @@ package service
 
 import (
 	"fmt"
+	"time"
 
 	"dbview/service/internal/common/manager/storage"
 	"dbview/service/internal/common/manager/storage/types"
@@ -230,13 +231,13 @@ func GetConnectionGroupTree(loadConnections bool) (*api.ConnectionGroupResponse,
 	return convertConnectionGroupToResponse(tree), nil
 }
 
-// CreateSQLScript 创建SQL脚本
-func CreateSQLScript(req *api.CreateSQLScriptRequest) (*api.SQLScriptResponse, error) {
+// CreateScript 创建脚本
+func CreateScript(req *api.CreateScriptRequest) (*api.ScriptResponse, error) {
 	if storageManager == nil {
 		return nil, fmt.Errorf("存储管理器未初始化")
 	}
 
-	script, err := storageManager.CreateSQLScript(
+	script, err := storageManager.CreateScript(
 		req.ConnectionID,
 		req.GroupID,
 		req.Name,
@@ -245,92 +246,115 @@ func CreateSQLScript(req *api.CreateSQLScriptRequest) (*api.SQLScriptResponse, e
 		req.Favorite,
 	)
 	if err != nil {
-		return nil, fmt.Errorf("创建SQL脚本失败,名称: %s,错误: %w", req.Name, err)
+		return nil, fmt.Errorf("创建脚本失败,名称: %s,错误: %w", req.Name, err)
 	}
 
 	// 转换为API响应格式
-	return convertSQLScriptToResponse(script), nil
+	return convertScriptToResponse(script), nil
 }
 
-// GetSQLScript 获取SQL脚本详情
-func GetSQLScript(scriptID string) (*api.SQLScriptResponse, error) {
+// GetScript 获取脚本详情
+func GetScript(scriptID string) (*api.ScriptResponse, error) {
 	if storageManager == nil {
 		return nil, fmt.Errorf("存储管理器未初始化")
 	}
 
-	script, err := storageManager.GetSQLScript(scriptID)
+	script, err := storageManager.GetScript(scriptID)
 	if err != nil {
-		return nil, fmt.Errorf("获取SQL脚本失败,脚本ID: %s,错误: %w", scriptID, err)
+		return nil, fmt.Errorf("获取脚本失败,脚本ID: %s,错误: %w", scriptID, err)
 	}
 
 	// 转换为API响应格式
-	return convertSQLScriptToResponse(script), nil
+	return convertScriptToResponse(script), nil
 }
 
-// UpdateSQLScript 更新SQL脚本
-func UpdateSQLScript(scriptID string, req *api.UpdateSQLScriptRequest) (*api.SQLScriptResponse, error) {
+// UpdateScript 更新脚本
+func UpdateScript(scriptID string, req *api.UpdateScriptRequest) (*api.ScriptResponse, error) {
 	if storageManager == nil {
 		return nil, fmt.Errorf("存储管理器未初始化")
 	}
 
-	script, err := storageManager.UpdateSQLScript(
+	// 获取当前脚本以支持部分更新(请求字段为指针)
+	existing, err := storageManager.GetScript(scriptID)
+	if err != nil {
+		return nil, fmt.Errorf("获取现有脚本失败,脚本ID: %s,错误: %w", scriptID, err)
+	}
+
+	name := existing.Name
+	if req.Name != nil {
+		name = *req.Name
+	}
+	description := existing.Description
+	if req.Description != nil {
+		description = *req.Description
+	}
+	content := existing.Content
+	if req.Content != nil {
+		content = *req.Content
+	}
+	favorite := existing.Favorite
+	if req.Favorite != nil {
+		favorite = *req.Favorite
+	}
+
+	script, err := storageManager.UpdateScript(
 		scriptID,
-		req.Name,
-		req.Description,
-		req.Content,
-		req.Favorite,
+		name,
+		description,
+		content,
+		favorite,
 	)
 	if err != nil {
-		return nil, fmt.Errorf("更新SQL脚本失败,脚本ID: %s,错误: %w", scriptID, err)
+		return nil, fmt.Errorf("更新脚本失败,脚本ID: %s,错误: %w", scriptID, err)
 	}
 
 	// 转换为API响应格式
-	return convertSQLScriptToResponse(script), nil
+	return convertScriptToResponse(script), nil
 }
 
-// DeleteSQLScript 删除SQL脚本
-func DeleteSQLScript(scriptID string) error {
+// DeleteScript 删除脚本
+func DeleteScript(scriptID string) error {
 	if storageManager == nil {
 		return fmt.Errorf("存储管理器未初始化")
 	}
 
-	err := storageManager.DeleteSQLScript(scriptID)
+	err := storageManager.DeleteScript(scriptID)
 	if err != nil {
-		return fmt.Errorf("删除SQL脚本失败,脚本ID: %s,错误: %w", scriptID, err)
+		return fmt.Errorf("删除脚本失败,脚本ID: %s,错误: %w", scriptID, err)
 	}
 
 	return nil
 }
 
-// UpdateSQLScriptExecutionStats 更新SQL脚本执行统计
-func UpdateSQLScriptExecutionStats(scriptID string) error {
+// UpdateScriptExecutionStats 更新脚本执行统计
+func UpdateScriptExecutionStats(scriptID string) error {
 	if storageManager == nil {
 		return fmt.Errorf("存储管理器未初始化")
 	}
 
-	err := storageManager.UpdateSQLScriptExecutionStats(scriptID)
+	err := storageManager.UpdateScriptExecutionStats(scriptID)
 	if err != nil {
-		return fmt.Errorf("更新SQL脚本执行统计失败,脚本ID: %s,错误: %w", scriptID, err)
+		return fmt.Errorf("更新脚本执行统计失败,脚本ID: %s,错误: %w", scriptID, err)
 	}
 
 	return nil
 }
 
-// ListSQLScripts 获取指定连接的所有SQL脚本
-func ListSQLScripts(connID string) ([]*api.SQLScriptResponse, error) {
+// ListScripts 获取指定连接的所有脚本
+func ListScripts(connID string) ([]*api.ScriptResponse, error) {
 	if storageManager == nil {
 		return nil, fmt.Errorf("存储管理器未初始化")
 	}
 
-	scripts, err := storageManager.ListSQLScripts(connID)
+	scripts, err := storageManager.ListScripts(connID)
 	if err != nil {
-		return nil, fmt.Errorf("获取SQL脚本列表失败,连接ID: %s,错误: %w", connID, err)
+		return nil, fmt.Errorf("获取脚本列表失败,连接ID: %s,错误: %w", connID, err)
 	}
 
 	// 转换为API响应格式
-	var result []*api.SQLScriptResponse
+	var result []*api.ScriptResponse
 	for _, script := range scripts {
-		result = append(result, convertSQLScriptToResponse(&script))
+		result = append(result, convertScriptToResponse(&script))
 	}
 
 	return result, nil
@@ -364,19 +388,45 @@ func CreateConnection(req *api.CreateConnectionRequest) (*api.DBConnectionRespon
 		displayOrder = maxOrder + 1
 	}
 
+	// 根据请求中的 kind 与嵌套 detail 提取实际字段
+	kind := req.Kind
+	var connType, connVersion, server, username, password, databaseName, connStr string
+	var port int
+	var useSSHTunnel bool
+
+	if kind == "database" && req.DBDetail != nil {
+		connType = req.DBDetail.Type
+		connVersion = req.DBDetail.Version
+		server = req.DBDetail.Server
+		port = req.DBDetail.Port
+		username = req.DBDetail.Username
+		password = req.DBDetail.Password
+		databaseName = req.DBDetail.Database
+		connStr = req.DBDetail.ConnectionString
+		useSSHTunnel = req.DBDetail.UseSSHTunnel
+	} else if kind == "server" && req.ServerDetail != nil {
+		connType = req.ServerDetail.Type
+		connVersion = req.ServerDetail.Version
+		server = req.ServerDetail.Server
+		port = req.ServerDetail.Port
+		username = req.ServerDetail.Username
+		// server detail doesn't include password/connection string/ssh tunnel
+	}
+
 	conn, err := storageManager.CreateConnection(
 		req.GroupID,
 		req.Name,
 		req.Description,
-		req.DBType,
-		req.DBVersion,
-		req.Server,
-		req.Port,
-		req.Username,
-		req.Password,
-		req.Database,
-		req.ConnectionString,
-		req.UseSSHTunnel,
+		kind,
+		connType,
+		connVersion,
+		server,
+		port,
+		username,
+		password,
+		databaseName,
+		connStr,
+		useSSHTunnel,
 		req.Color,
 		req.AutoConnect,
 		displayOrder,
@@ -412,21 +462,72 @@ func UpdateConnection(connID string, req *api.UpdateConnectionRequest) (*api.DBC
 
 	// 转换为内部更新请求结构体
 	updateReq := &types.UpdateConnectionRequest{
-		GroupID:          req.GroupID,
-		Name:             req.Name,
-		Description:      req.Description,
-		DBType:           req.DBType,
-		DBVersion:        req.DBVersion,
-		Server:           req.Server,
-		Port:             req.Port,
-		Username:         req.Username,
-		Password:         req.Password,
-		Database:         req.Database,
-		ConnectionString: req.ConnectionString,
-		UseSSHTunnel:     req.UseSSHTunnel,
-		Color:            req.Color,
-		AutoConnect:      req.AutoConnect,
-		DisplayOrder:     req.DisplayOrder,
+		GroupID:      req.GroupID,
+		Name:         req.Name,
+		Description:  req.Description,
+		Kind:         req.Type,
+		Color:        req.Color,
+		AutoConnect:  req.AutoConnect,
+		DisplayOrder: req.DisplayOrder,
+	}
+
+	// 如果提供了 DBDetailUpdate,则将其字段映射到内部 UpdateConnectionRequest
+	if req.DBDetail != nil {
+		if req.DBDetail.Type != nil {
+			updateReq.Type = req.DBDetail.Type
+		}
+		if req.DBDetail.Version != nil {
+			updateReq.Version = req.DBDetail.Version
+		}
+		if req.DBDetail.Server != nil {
+			updateReq.Server = req.DBDetail.Server
+		}
+		if req.DBDetail.Port != nil {
+			updateReq.Port = req.DBDetail.Port
+		}
+		if req.DBDetail.Username != nil {
+			updateReq.Username = req.DBDetail.Username
+		}
+		if req.DBDetail.Password != nil {
+			updateReq.Password = req.DBDetail.Password
+		}
+		if req.DBDetail.Database != nil {
+			updateReq.Database = req.DBDetail.Database
+		}
+		if req.DBDetail.ConnectionString != nil {
+			updateReq.ConnectionString = req.DBDetail.ConnectionString
+		}
+		if req.DBDetail.UseSSHTunnel != nil {
+			updateReq.UseSSHTunnel = req.DBDetail.UseSSHTunnel
+		}
+	}
+
+	// 如果提供了 ServerDetailUpdate,则将其字段映射
+	if req.ServerDetail != nil {
+		if req.ServerDetail.Type != nil {
+			updateReq.Type = req.ServerDetail.Type
+		}
+		if req.ServerDetail.Version != nil {
+			updateReq.Version = req.ServerDetail.Version
+		}
+		if req.ServerDetail.Server != nil {
+			updateReq.Server = req.ServerDetail.Server
+		}
+		if req.ServerDetail.Port != nil {
+			updateReq.Port = req.ServerDetail.Port
+		}
+		if req.ServerDetail.Username != nil {
+			updateReq.Username = req.ServerDetail.Username
+		}
+		if req.ServerDetail.AuthType != nil {
+			updateReq.AuthType = req.ServerDetail.AuthType
+		}
+		if req.ServerDetail.PrivateKey != nil {
+			updateReq.PrivateKey = req.ServerDetail.PrivateKey
+		}
+		if req.ServerDetail.UseSudo != nil {
+			updateReq.UseSudo = req.ServerDetail.UseSudo
+		}
 	}
 
 	conn, err := storageManager.UpdateConnection(connID, updateReq)
@@ -537,38 +638,73 @@ func GetScriptGroupTree(loadScripts bool) (*api.ScriptGroupResponse, error) {
 // 转换类型辅助函数
 // 将数据库模型转换为API响应格式
 
-func convertDBConnectionToResponse(conn *types.DBConnection) *api.DBConnectionResponse {
+func convertDBConnectionToResponse(conn *types.ConnectionWithDetails) *api.DBConnectionResponse {
 	if conn == nil {
 		return nil
 	}
 
-	var scripts []*api.SQLScriptResponse
+	var scripts []*api.ScriptResponse
 	for _, script := range conn.Scripts {
-		scripts = append(scripts, convertSQLScriptToResponse(&script))
-	}
-
-	return &api.DBConnectionResponse{
-		ID:               conn.ID,
-		GroupID:          conn.GroupID,
-		Name:             conn.Name,
-		Description:      conn.Description,
-		DBType:           conn.DBType,
-		DBVersion:        conn.DBVersion,
-		Server:           conn.Server,
-		Port:             conn.Port,
-		Database:         conn.Database,
-		Username:         conn.Username,
-		Password:         conn.Password,
-		UseSSHTunnel:     conn.UseSSHTunnel,
-		ConnectionString: conn.ConnectionString,
-		Color:            conn.Color,
-		LastConnected:    conn.LastConnected,
-		AutoConnect:      conn.AutoConnect,
-		DisplayOrder:     conn.DisplayOrder,
-		Scripts:          scripts,
-		CreatedAt:        conn.CreatedAt,
-		UpdatedAt:        conn.UpdatedAt,
+		scripts = append(scripts, convertScriptToResponse(&script))
+	}
+
+	// 根据存储的 detail 构造嵌套响应
+	var dbDetail *api.DBDetail
+	var serverDetail *api.ServerDetail
+	var lastConnected time.Time
+
+	if conn.DBDetail != nil {
+		dbDetail = &api.DBDetail{
+			Type:             conn.DBDetail.Type,
+			Version:          conn.DBDetail.Version,
+			Server:           conn.DBDetail.Server,
+			Port:             conn.DBDetail.Port,
+			Username:         conn.DBDetail.Username,
+			Password:         conn.DBDetail.Password,
+			Database:         conn.DBDetail.DatabaseName,
+			ConnectionString: conn.DBDetail.ConnectionString,
+			UseSSHTunnel:     conn.DBDetail.UseSSHTunnel,
+		}
+		lastConnected = conn.DBDetail.LastConnected
+	} else if conn.ServerDetail != nil {
+		serverDetail = &api.ServerDetail{
+			Type:       conn.ServerDetail.Type,
+			Version:    conn.ServerDetail.Version,
+			Server:     conn.ServerDetail.Server,
+			Port:       conn.ServerDetail.Port,
+			Username:   conn.ServerDetail.Username,
+			AuthType:   conn.ServerDetail.AuthType,
+			PrivateKey: conn.ServerDetail.PrivateKey,
+			UseSudo:    conn.ServerDetail.UseSudo,
+		}
 	}
+
+	// Only populate details based on explicit Kind. No compatibility fallbacks.
+	kind := conn.Connection.Kind
+
+	resp := &api.DBConnectionResponse{
+		ID:            conn.Connection.ID,
+		GroupID:       conn.Connection.GroupID,
+		Name:          conn.Connection.Name,
+		Description:   conn.Connection.Description,
+		Kind:          kind,
+		Color:         conn.Connection.Color,
+		LastConnected: lastConnected,
+		AutoConnect:   conn.Connection.AutoConnect,
+		DisplayOrder:  conn.Connection.DisplayOrder,
+		Scripts:       scripts,
+		CreatedAt:     conn.Connection.CreatedAt,
+		UpdatedAt:     conn.Connection.UpdatedAt,
+	}
+
+	// Only attach the appropriate nested detail according to explicit Kind
+	if kind == "database" {
+		resp.DBDetail = dbDetail
+	} else if kind == "server" {
+		resp.ServerDetail = serverDetail
+	}
+
+	return resp
 }
 
 func convertConnectionGroupToResponse(group *types.ConnectionGroup) *api.ConnectionGroupResponse {
@@ -600,12 +736,12 @@ func convertConnectionGroupToResponse(group *types.ConnectionGroup) *api.Connect
 	}
 }
 
-func convertSQLScriptToResponse(script *types.SQLScript) *api.SQLScriptResponse {
+func convertScriptToResponse(script *types.Script) *api.ScriptResponse {
 	if script == nil {
 		return nil
 	}
 
-	return &api.SQLScriptResponse{
+	return &api.ScriptResponse{
 		ID:             script.ID,
 		ConnectionID:   script.ConnectionID,
 		GroupID:        script.GroupID,
@@ -613,7 +749,13 @@ func convertSQLScriptToResponse(script *types.SQLScript) *api.SQLScriptResponse
 		Description:    script.Description,
 		Content:        script.Content,
 		Favorite:       script.Favorite,
+		Language:       script.Language,
+		Metadata:       script.Metadata,
+		Tags:           script.Tags,
+		Enabled:        script.Enabled,
+		Owner:          script.Owner,
 		LastExecuted:   script.LastExecuted,
+		LastRunStatus:  script.LastRunStatus,
 		ExecutionCount: script.ExecutionCount,
 		CreatedAt:      script.CreatedAt,
 		UpdatedAt:      script.UpdatedAt,
@@ -625,9 +767,9 @@ func convertScriptGroupToResponse(group *types.ScriptGroup) *api.ScriptGroupResp
 		return nil
 	}
 
-	var scripts []*api.SQLScriptResponse
+	var scripts []*api.ScriptResponse
 	for _, script := range group.Scripts {
-		scripts = append(scripts, convertSQLScriptToResponse(&script))
+		scripts = append(scripts, convertScriptToResponse(&script))
 	}
 
 	var children []*api.ScriptGroupResponse
@@ -712,12 +854,3 @@ func getMaxDisplayOrderForConnections(groupID string) (int, error) {
 
 	return maxOrder, nil
 }
-
-// getConnectionDetails 获取连接详细信息
-func getConnectionDetails(connID string) string {
-	conn, err := storageManager.GetConnection(connID)
-	if err != nil {
-		return fmt.Sprintf("连接ID: %s", connID)
-	}
-	return fmt.Sprintf("连接名称: %s,数据库类型: %s,服务器: %s:%d", conn.Name, conn.DBType, conn.Server, conn.Port)
-}