GTong 8 tháng trước cách đây
mục cha
commit
80839f0dc2
70 tập tin đã thay đổi với 4213 bổ sung400 xóa
  1. 2 0
      .gitignore
  2. 16 0
      .vscode/launch.json
  3. 81 0
      api/Resopnse/Resopnse.go
  4. 41 4
      api/api.go
  5. 71 0
      api/err_code/err_code.go
  6. 0 11
      config/127.0.0.1 copy/listen.toml
  7. 0 11
      config/127.0.0.1/listen.toml
  8. 1 0
      config/default_filename/127.0.0.1_os_info_20241024_172212.toml
  9. 1 0
      config/default_filename/127.0.0.1_os_info_20241024_173549.toml
  10. 1 0
      config/default_filename/127.0.0.1_os_info_20241024_174030.toml
  11. 1 0
      config/default_filename/127.0.0.1_os_info_20241024_174918.toml
  12. 1 0
      config/default_filename/127.0.0.1_os_info_20241025_093002.toml
  13. 0 11
      config/test/listen.toml
  14. 30 0
      config/集群1/listen.toml
  15. 0 1
      config/集群4/127.0.0.1_os_info_20241023_103457.toml
  16. 0 1
      config/集群4/127.0.0.1_os_info_20241023_190737.toml
  17. 1 0
      config/集群4/127.0.0.1_os_info_20241024_171028.toml
  18. 1 0
      config/集群4/127.0.0.1_os_info_20241024_171207.toml
  19. 1 0
      config/集群4/127.0.0.1_os_info_20241025_100513.toml
  20. 20 0
      config/集群4/listen.toml
  21. 5 1
      go.mod
  22. 4 0
      go.sum
  23. 0 25
      internal/controllers/connect/connect.go
  24. 46 0
      internal/controllers/connect/connect_controllers.go
  25. 0 11
      internal/controllers/system/system_info.go
  26. 97 0
      internal/controllers/system/system_info_controller.go
  27. 22 5
      internal/global/init.go
  28. 45 0
      internal/middleware/ProgressManager.go
  29. 0 114
      internal/middleware/cache copy.go
  30. 124 88
      internal/middleware/cache.go
  31. 3 1
      internal/middleware/test/cache_test.go
  32. 119 0
      internal/middleware/utils/utils.go
  33. 12 6
      internal/models/cache_models.go
  34. 1 0
      internal/models/system_models.go
  35. 57 0
      internal/router.go
  36. 69 30
      internal/services/connect/connect.go
  37. 90 46
      internal/services/system/system_services.go
  38. 46 31
      internal/services/test/connect_test.go
  39. 6 0
      internal/services/test/dba.toml
  40. 0 0
      internal/services/test/logs/log-2024-10-21.log
  41. 14 0
      internal/services/test/logs/log-2024-10-22.log
  42. 0 0
      internal/services/test/logs/log-2024-10-25.log
  43. 49 0
      internal/services/test/system_test.go
  44. 4 0
      internal/services/xugu_serices/selcet.go
  45. 0 0
      logs/log-2024-10-18.log
  46. 0 0
      logs/log-2024-10-21.log
  47. 28 0
      logs/log-2024-10-22.log
  48. 14 0
      logs/log-2024-10-23.log
  49. 7 0
      logs/log-2024-10-24.log
  50. 0 0
      logs/log-2024-10-25.log
  51. 6 2
      main.go
  52. 1 1
      module/logger/logs.go
  53. 91 0
      pkg/xugu/xugu_buffer.go
  54. 392 0
      pkg/xugu/xugu_conn.go
  55. 64 0
      pkg/xugu/xugu_connector.go
  56. 32 0
      pkg/xugu/xugu_define.go
  57. 51 0
      pkg/xugu/xugu_driver.go
  58. 207 0
      pkg/xugu/xugu_fields.go
  59. 85 0
      pkg/xugu/xugu_model.go
  60. 520 0
      pkg/xugu/xugu_parse.go
  61. 156 0
      pkg/xugu/xugu_parse_time.go
  62. 48 0
      pkg/xugu/xugu_result.go
  63. 196 0
      pkg/xugu/xugu_rows.go
  64. 130 0
      pkg/xugu/xugu_sock.go
  65. 139 0
      pkg/xugu/xugu_stmt.go
  66. 36 0
      pkg/xugu/xugu_tranx.go
  67. 189 0
      pkg/xugu/xugu_utils.go
  68. 385 0
      static/index.html
  69. 39 0
      static/script.js
  70. 315 0
      static/styles.css

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+logs\
+config\

+ 16 - 0
.vscode/launch.json

@@ -0,0 +1,16 @@
+{
+    // 使用 IntelliSense 了解相关属性。 
+    // 悬停以查看现有属性的描述。
+    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
+  
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Launch Package",
+            "type": "go",
+            "request": "launch",
+            "mode": "auto",
+            "program": "${workspaceFolder}/main.go"
+        }
+    ]
+}

+ 81 - 0
api/Resopnse/Resopnse.go

@@ -0,0 +1,81 @@
+package resopnse
+
+import (
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+)
+
+// 这里定义的状态码
+type Rescode int64
+
+// 在const里使用了iota自增
+const (
+	CodeSuccess Rescode = 1000 + iota
+	CodeInvalidParam
+	CodeUserExist
+	CodeUserNotExist
+	CodeNotMatch
+	CodeServerBusy
+)
+
+// 给对应状态码加上消息
+var codeMsgMap = map[Rescode]string{
+	CodeSuccess:      "success",
+	CodeInvalidParam: "请求参数错误",
+	CodeUserExist:    "用户名已存在",
+	CodeUserNotExist: "用户不存在",
+	CodeNotMatch:     "用户名或密码错误",
+	CodeServerBusy:   "服务器繁忙",
+	// 比如数据库连接错误啥的,前端不用知道,
+	// 就说服务器繁忙就行
+}
+
+// 获得对应状态码的消息
+func (c Rescode) GetMsg() string {
+	msg, ok := codeMsgMap[c]
+	if !ok {
+		msg = codeMsgMap[CodeServerBusy]
+	}
+	return msg
+}
+
+/*
+封装响应方法,固定格式,方便前端
+
+	{
+		"code":  , //程序中的错误码
+		"msg":     ,// 错误的提示信息
+		"data":{}      //数据
+	}
+*/
+type ResopnseDate struct {
+	Code Rescode     `json:"code"`
+	Msg  interface{} `json:"msg"`
+	Date interface{} `json:"data"`
+}
+
+func ResponseError(c *gin.Context, code Rescode) {
+	//只有状态码
+	c.JSON(http.StatusOK, &ResopnseDate{
+		Code: code,
+		Msg:  code.GetMsg(),
+		Date: nil,
+	})
+}
+func ResponseErrorWithMsg(c *gin.Context, code Rescode, msg interface{}) {
+	//有状态码和自己的信息
+	c.JSON(http.StatusOK, &ResopnseDate{
+		Code: code,
+		Msg:  msg,
+		Date: nil,
+	})
+}
+func ResopnseSuccess(c *gin.Context, data interface{}) {
+	//成功以后返回的,自带数据
+	c.JSON(http.StatusOK, &ResopnseDate{
+		Code: CodeSuccess,
+		Msg:  CodeSuccess.GetMsg(),
+		Date: data,
+	})
+}

+ 41 - 4
api/api.go

@@ -1,12 +1,23 @@
 package api
 
-// 定义结构体
+/* ----------- 连接返回信息------------------    */
+// 服务器和数据库连接信息
 type ConnectInfoRequest struct {
-	Id  string  `json:"id" toml:"id"`
-	Ssh SshInfo `json:"ssh_info" toml:"server"`
-	Db  DbInfo  `json:"db_info" toml:"db"`
+	//	Id  string  `json:"id" toml:"id"`
+	Ssh SshInfo `json:"ssh_info"`
+	Db  DbInfo  `json:"db_info"`
 }
 
+// 返回服务器和数据库连接信息
+type ConnectInfoResopnse struct {
+	BaseFileName string                 `json:"base_filename" toml:"base_filename"`
+	ConnInfo     map[string]ConnectInfo `json:"conn_info"`
+}
+
+type ConnectInfo struct {
+	Ssh SshInfo `json:"ssh_info"`
+	Db  DbInfo  `json:"db_info"`
+}
 type SshInfo struct {
 	Username string `json:"username" toml:"ssh_username"`
 	Password string `json:"password" toml:"ssh_password"`
@@ -20,3 +31,29 @@ type DbInfo struct {
 	Database string `json:"database" toml:"db_database"`
 	Port     string `json:"port" toml:"db_port"`
 }
+
+func SetConnectInfo(sshName, sshpw, sship, sshport, dbname, dbuser, dbpw, dbport string) ConnectInfo {
+	return ConnectInfo{
+		Ssh: SshInfo{
+			Username: sshName,
+			Password: sshpw,
+			Host:     sship,
+			Port:     sshport,
+		},
+		Db: DbInfo{
+			User:     dbuser,
+			Password: dbpw,
+			Database: dbname,
+			Port:     dbport,
+		},
+	}
+}
+
+/* ----------- 服务器信息------------------    */
+type ServerInfoRequest struct {
+	BaseFileName string `json:"base_filename" toml:"base_filename"`
+	Username     string `json:"username" toml:"ssh_username"`
+	Password     string `json:"password" toml:"ssh_password"`
+	Host         string `json:"host" toml:"ssh_ip"`
+	Port         string `json:"port" toml:"ssh_port"`
+}

+ 71 - 0
api/err_code/err_code.go

@@ -0,0 +1,71 @@
+package main
+
+import (
+	"fmt"
+
+	"github.com/pkg/errors"
+)
+
+// Response 错误时返回自定义结构
+type Response struct {
+	Code      ErrCode `json:"code"`       // 错误码
+	Msg       string  `json:"msg"`        // 错误信息
+	RequestId string  `json:"request_id"` // 请求ID
+}
+
+type ErrCode int // 错误码
+
+// 定义 errorCode
+const (
+	// ServerError 1开头为服务级错误码
+	ServerError    ErrCode = 10001
+	ParamBindError ErrCode = 10002
+
+	// IllegalDatasetName 2开头为业务级错误码
+	// 其中数据集管理为201开头
+	IllegalDatasetName ErrCode = 20101 // 无效的数据集名称
+	ParamNameError     ErrCode = 20102 // 参数name错误
+
+	// IllegalPhoneNum 用户管理模块:202开头
+	IllegalPhoneNum         ErrCode = 20201 // 手机号格式不正确
+	IllegalVerifyCode       ErrCode = 20202 // 无效的验证码
+	PhoneRepeatedRegistered ErrCode = 20203 // 手机号不可重复注册
+	PhoneIsNotRegistered    ErrCode = 20204 // 该手机号未注册
+	PhoneRepeatedApproved   ErrCode = 20205 // 手机号不可重复审批
+	PhoneIsNotApproved      ErrCode = 20206 // 该手机号未审批
+
+	// IllegalModelName 预训练模块:203开头
+	IllegalModelName ErrCode = 20301 // 非法模型名称
+)
+
+// 定义 errorCode 对应的文本信息
+var errorMsg = map[ErrCode]string{
+	ServerError:        "服务内部错误",
+	ParamBindError:     "参数信息有误",
+	IllegalDatasetName: "无效的数据集名称",
+	ParamNameError:     "参数name错误",
+	IllegalPhoneNum:    "手机号格式不正确",
+	IllegalModelName:   "非法模型名称",
+}
+
+// Text 根据错误码获取错误信息
+func String(code ErrCode) string {
+	if msg, ok := errorMsg[code]; ok {
+		return msg
+	}
+	return "未知错误码"
+}
+
+// Error 实现 error 接口
+func (r *Response) Error() string {
+	return fmt.Sprintf("Code: %d, Msg: %s, RequestId: %s", r.Code, r.Msg, r.RequestId)
+}
+
+// NewCustomError 新建自定义 error 实例化
+func NewCustomError(code ErrCode, requestId string) error {
+	return errors.Wrap(&Response{
+		Code:      code,
+		Msg:       String(code),
+		RequestId: requestId,
+	}, "custom error")
+}

+ 0 - 11
config/127.0.0.1 copy/listen.toml

@@ -1,11 +0,0 @@
-[db]
-db_database = 'test'
-db_password = 'SYSDBA'
-db_port = '5236'
-db_user = 'SYSDBA'
-
-[server]
-ssh_ip = '122.0.0.9'
-ssh_password = '845895'
-ssh_port = '22'
-ssh_username = 'gtong'

+ 0 - 11
config/127.0.0.1/listen.toml

@@ -1,11 +0,0 @@
-[db]
-db_database = 'test'
-db_password = 'SYSDBA'
-db_port = '5236'
-db_user = 'SYSDBA'
-
-[server]
-ssh_ip = '127.0.0.1'
-ssh_password = '845895'
-ssh_port = '22'
-ssh_username = 'gtong'

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 0
config/default_filename/127.0.0.1_os_info_20241024_172212.toml


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 0
config/default_filename/127.0.0.1_os_info_20241024_173549.toml


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 0
config/default_filename/127.0.0.1_os_info_20241024_174030.toml


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 0
config/default_filename/127.0.0.1_os_info_20241024_174918.toml


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 0
config/default_filename/127.0.0.1_os_info_20241025_093002.toml


+ 0 - 11
config/test/listen.toml

@@ -1,11 +0,0 @@
-[db]
-db_database = 'test'
-db_password = 'SYSDBA'
-db_port = '5236'
-db_user = 'SYSDBA'
-
-[server]
-ssh_ip = '122.01.0.9'
-ssh_password = '845895'
-ssh_port = '22'
-ssh_username = 'gtong'

+ 30 - 0
config/集群1/listen.toml

@@ -0,0 +1,30 @@
+[server_db]
+[server_db.1]
+db_database = '456'
+db_password = '156'
+db_port = '4156'
+db_user = '123'
+ssh_ip = '12.56.23.6'
+ssh_password = '132'
+ssh_port = '22'
+ssh_username = 'root'
+
+[server_db.2]
+db_database = '456'
+db_password = '156'
+db_port = '4156'
+db_user = '123'
+ssh_ip = '12.56.23.7'
+ssh_password = '132'
+ssh_port = '22'
+ssh_username = 'root'
+
+[server_db.3]
+db_database = '456'
+db_password = '156'
+db_port = '4156'
+db_user = '123'
+ssh_ip = '12.56.23.8'
+ssh_password = '132'
+ssh_port = '22'
+ssh_username = 'root'

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 1
config/集群4/127.0.0.1_os_info_20241023_103457.toml


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 1
config/集群4/127.0.0.1_os_info_20241023_190737.toml


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 0
config/集群4/127.0.0.1_os_info_20241024_171028.toml


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 0
config/集群4/127.0.0.1_os_info_20241024_171207.toml


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 0
config/集群4/127.0.0.1_os_info_20241025_100513.toml


+ 20 - 0
config/集群4/listen.toml

@@ -0,0 +1,20 @@
+[server_db]
+[server_db.1]
+db_database = 'test'
+db_password = 'SYSDBA'
+db_port = '5236'
+db_user = 'SYSDBA'
+ssh_ip = '127.0.0.1'
+ssh_password = '845895'
+ssh_port = '22'
+ssh_username = 'gtong'
+
+[server_db.2]
+db_database = 'test'
+db_password = 'SYSDBA'
+db_port = '5231'
+db_user = 'SYSDBA'
+ssh_ip = '127.0.0.2'
+ssh_password = '845895'
+ssh_port = '22'
+ssh_username = 'gtong'

+ 5 - 1
go.mod

@@ -5,6 +5,9 @@ go 1.22.2
 require (
 	github.com/BurntSushi/toml v1.4.0
 	github.com/gin-gonic/gin v1.10.0
+	github.com/google/uuid v1.4.0
+	github.com/mitchellh/mapstructure v1.5.0
+	github.com/pkg/errors v0.9.1
 	github.com/sirupsen/logrus v1.9.3
 	github.com/spf13/viper v1.19.0
 	golang.org/x/crypto v0.27.0
@@ -28,7 +31,6 @@ require (
 	github.com/leodido/go-urn v1.4.0 // indirect
 	github.com/magiconair/properties v1.8.7 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
-	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
@@ -52,3 +54,5 @@ require (
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
+
+replace xg_dba/pkg/xugu => C:/Program_GT/Code/Go/Work/xugu/xugu_go_driver/xugu

+ 4 - 0
go.sum

@@ -35,6 +35,8 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
+github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -62,6 +64,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
 github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=

+ 0 - 25
internal/controllers/connect/connect.go

@@ -1,25 +0,0 @@
-package connect
-
-import (
-	"net/http"
-	"xg_dba/api"
-	services "xg_dba/internal/services/connect"
-
-	"github.com/gin-gonic/gin"
-)
-
-// 获取连接信息
-func SaveConnectInfo_controller(c *gin.Context) {
-	connectInfo := api.ConnectInfoRequest{}
-	if err := c.BindJSON(&connectInfo); err != nil {
-		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
-		return
-	}
-	//存储到本地
-	localPath := "./config/" + connectInfo.Ssh.Host + "/" + connectInfo.Ssh.Host + ".toml"
-	if err := services.SetConnectInfo_service(connectInfo, localPath); err != nil {
-		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
-		return
-	}
-	c.JSON(http.StatusOK, connectInfo)
-}

+ 46 - 0
internal/controllers/connect/connect_controllers.go

@@ -0,0 +1,46 @@
+package connect
+
+import (
+	"net/http"
+	"xg_dba/api"
+	"xg_dba/internal/global"
+	services "xg_dba/internal/services/connect"
+
+	"github.com/gin-gonic/gin"
+)
+
+// 获取连接信息
+func GetConnectInfo_controller(c *gin.Context) {
+	if data, err := global.Cache.GetConnectCache(); err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+		return
+	} else {
+		//		fmt.Printf("get connect info from cache :%#v :", data)
+		c.JSON(http.StatusOK, gin.H{"message": "success",
+			"data": data,
+		})
+	}
+
+}
+
+// 保存连接信息
+func AddConnectInfo_controller(c *gin.Context) {
+	connectInfo := []api.ConnectInfoRequest{}
+	if err := c.BindJSON(&connectInfo); err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+		return
+	}
+
+	//判断地址合法性
+	if connectInfo[0].Ssh.Host == "" {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "host不能为空"})
+		return
+	}
+	//存储到本地
+	localPath := "./config/" + connectInfo[0].Ssh.Host + "/" + "listen" + ".toml"
+	if err := services.SetConnectInfo_service(connectInfo, localPath); err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+		return
+	}
+	c.JSON(http.StatusOK, gin.H{"message": "success"})
+}

+ 0 - 11
internal/controllers/system/system_info.go

@@ -1,11 +0,0 @@
-package system
-
-import (
-	"github.com/gin-gonic/gin"
-)
-
-// 设置系统信息
-func SetSystemInfo_controller(c *gin.Context) {
-	//获取连接信息
-
-}

+ 97 - 0
internal/controllers/system/system_info_controller.go

@@ -0,0 +1,97 @@
+package system
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"xg_dba/api"
+
+	"xg_dba/internal/global"
+	system "xg_dba/internal/services/system"
+
+	"github.com/gin-gonic/gin"
+)
+
+// //获取缓存中所有连接信息
+func GetSingleSystemInfo_controller(c *gin.Context) {
+	//获取连接信息
+	connectInfo := api.ServerInfoRequest{}
+	if err := c.BindJSON(&connectInfo); err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+		return
+	}
+	if sysInfo, err := global.Cache.GetSingleSystemInfoCache(connectInfo.BaseFileName ,connectInfo.Host); err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+		return
+	} else {
+		c.JSON(http.StatusOK, gin.H{"message": "success",
+			"data": sysInfo,
+		})
+	}
+}
+
+// 设置系统信息(todo)
+func SetSystemInfo_controller(c *gin.Context) {
+	// 获取连接信息
+	connectInfo := []api.ServerInfoRequest{}
+	if err := c.BindJSON(&connectInfo); err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+		return
+	}
+	if connectInfo[0].BaseFileName == "" {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "BaseFileName is required"})
+		return
+	}
+	// 创建新的进度 channel 并返回进度 ID
+	progressID := global.Progress.CreateProgress()
+	// 启动任务的 goroutine
+	go func() {
+		system.SetSystemInfo_service(connectInfo, fmt.Sprintf("./config/%s/", connectInfo[0].BaseFileName), progressID)
+	}()
+
+	// 返回给前端 progress ID
+	//	c.JSON(http.StatusOK, gin.H{"progress_id": progressID})
+
+	c.JSON(http.StatusOK, gin.H{"message": "success",
+		"progressID": progressID,
+	})
+	// //使用 Stream 处理实时推送
+	// c.Stream(func(w io.Writer) bool {
+	// 	if message, ok := <-progressChan; ok {
+	// 		c.SSEvent("progress", gin.H{"message": message})
+	// 		fmt.Println("Progress给:", message)
+	// 		return true
+	// 	}
+	// 	return false
+	// })
+}
+
+// 设置系统信息并推送进度
+func GetSystemInfoProgress_controller(c *gin.Context) {
+	// 解析请求参数
+	progressID := c.Query("progressID") // 获取查询参数 `id`,也就是 `progressID`
+	if progressID == "" {
+		c.JSON(400, gin.H{
+			"error": "progressID is required",
+		})
+		return
+	}
+
+	// 获取对应的进度 channel
+	progressChan, ok := global.Progress.GetProgressChan(progressID)
+	if !ok {
+		c.JSON(http.StatusNotFound, gin.H{"error": "Progress ID not found"})
+		return
+	}
+
+	// //使用 Stream 处理实时推送
+	c.Stream(func(w io.Writer) bool {
+		if message, ok := <-progressChan; ok {
+			c.SSEvent("progress", gin.H{"message": message})
+			fmt.Println("Progress给:", message)
+			return true
+		}
+		return false
+	})
+
+}

+ 22 - 5
internal/global/init.go

@@ -1,10 +1,27 @@
 package global
 
-import "fmt"
+import (
+	"fmt"
+	"log"
+	"xg_dba/internal/middleware"
+	"xg_dba/module/logger"
 
-func GlobalInit() {
+	"github.com/sirupsen/logrus"
+)
+
+var Logger *logrus.Logger
+var Cfg *Config
+var Cache *middleware.ServerDbCache
+var Progress *middleware.ProgressManager
+
+func GlobalInit(cachePath string) {
 	fmt.Println("Init function from package global")
-	cfg := LoadConfig("./dba.toml")
-	fmt.Println("cfg :", cfg)
-	InitLogs(cfg.Logs.AppLog, "info")
+	Cfg = LoadConfig("./dba.toml")
+	fmt.Println("cfg :", Cfg)
+	Logger = logger.InitLogs(Cfg.Logs.AppLog, "info")
+	var err error
+	if Cache, err = middleware.InitCache(cachePath); err != nil {
+		log.Fatalf("无法解析配置文件: %s", err)
+	}
+	Progress = middleware.NewProgressManager()
 }

+ 45 - 0
internal/middleware/ProgressManager.go

@@ -0,0 +1,45 @@
+package middleware
+
+import (
+	"sync"
+
+	"github.com/google/uuid"
+)
+
+// ProgressManager 用于管理所有请求的进度
+type ProgressManager struct {
+	progressMap map[string]chan string
+	mu          sync.RWMutex
+}
+
+func NewProgressManager() *ProgressManager {
+	return &ProgressManager{
+		progressMap: make(map[string]chan string),
+	}
+}
+
+func (pm *ProgressManager) CreateProgress() string {
+	pm.mu.Lock()
+	defer pm.mu.Unlock()
+	// 生成一个唯一的请求 ID
+	id := uuid.New().String()
+	// 创建一个新的 progress channel
+	pm.progressMap[id] = make(chan string)
+	return id
+}
+
+func (pm *ProgressManager) GetProgressChan(id string) (chan string, bool) {
+	pm.mu.RLock()
+	defer pm.mu.RUnlock()
+	progressChan, ok := pm.progressMap[id]
+	return progressChan, ok
+}
+
+func (pm *ProgressManager) CloseProgress(id string) {
+	pm.mu.Lock()
+	defer pm.mu.Unlock()
+	if progressChan, ok := pm.progressMap[id]; ok {
+		close(progressChan)
+		delete(pm.progressMap, id)
+	}
+}

+ 0 - 114
internal/middleware/cache copy.go

@@ -1,114 +0,0 @@
-package middleware
-
-// import (
-// 	"fmt"
-// 	"os"
-// 	"path/filepath"
-// 	"regexp"
-// 	"sort"
-// 	"xg_dba/internal/models"
-
-// 	"github.com/BurntSushi/toml"
-// )
-
-// func InitCache(folderPath string) {
-
-// 	packageModels := make(map[string]models.Cache)
-
-// 	err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		if info.IsDir() {
-// 			listenFilePath := filepath.Join(path, "listen.toml")
-
-// 			var connectInfo models.ConnectInfo
-// 			var systemMetaInfo models.SystemMetaInfo
-
-// 			if fileExists(listenFilePath) {
-// 				if _, err := parseTomlFile(listenFilePath, &connectInfo); err != nil {
-// 					return err
-// 				}
-// 			}
-
-// 			latestOsInfoFilePath, err := getLatestOsInfoFile(path)
-// 			if err != nil {
-// 				return err
-// 			}
-
-// 			if latestOsInfoFilePath != "" {
-// 				if _, err := parseTomlFile(latestOsInfoFilePath, &systemMetaInfo); err != nil {
-// 					return err
-// 				}
-// 			}
-
-// 			host := connectInfo.Ssh.Host
-// 			if host != "" {
-// 				packageModels[host] = models.Cache{
-// 					ConnectInfo:    connectInfo,
-// 					SystemMetaInfo: systemMetaInfo,
-// 				}
-// 			}
-// 		}
-// 		return nil
-// 	})
-
-// 	if err != nil {
-// 		fmt.Printf("Error walking the path %q: %v\n", folderPath, err)
-// 		return
-// 	}
-
-// 	for host, cache := range packageModels {
-// 		fmt.Printf("Host: %s, Cache: %+v\n", host, cache)
-// 	}
-// 	fmt.Println("个数为: ", len(packageModels))
-// }
-
-// func fileExists(filename string) bool {
-// 	info, err := os.Stat(filename)
-// 	if os.IsNotExist(err) {
-// 		return false
-// 	}
-// 	return !info.IsDir()
-// }
-
-// func parseTomlFile(filePath string, v interface{}) (toml.MetaData, error) {
-// 	file, err := os.Open(filePath)
-// 	if err != nil {
-// 		return toml.MetaData{}, err
-// 	}
-// 	defer file.Close()
-
-// 	decoder := toml.NewDecoder(file)
-// 	metaData, err := decoder.Decode(v)
-// 	if err != nil {
-// 		return toml.MetaData{}, err
-// 	}
-// 	return metaData, nil
-// }
-
-// func getLatestOsInfoFile(dirPath string) (string, error) {
-// 	pattern := regexp.MustCompile(`^os_info(?:_\d{8}_\d{6})?\.toml$`)
-// 	files, err := os.ReadDir(dirPath)
-// 	if err != nil {
-// 		return "", err
-// 	}
-
-// 	var matchingFiles []string
-// 	for _, file := range files {
-// 		if !file.IsDir() && pattern.MatchString(file.Name()) {
-// 			matchingFiles = append(matchingFiles, filepath.Join(dirPath, file.Name()))
-// 		}
-// 	}
-
-// 	if len(matchingFiles) == 0 {
-// 		return "", nil
-// 	}
-
-// 	sort.Slice(matchingFiles, func(i, j int) bool {
-// 		return matchingFiles[i] > matchingFiles[j]
-// 	})
-
-// 	return matchingFiles[0], nil
-// }

+ 124 - 88
internal/middleware/cache.go

@@ -2,142 +2,178 @@ package middleware
 
 import (
 	"fmt"
-	"os"
+	"io/fs"
 	"path/filepath"
-	"regexp"
-	"sort"
+	"xg_dba/api"
+	"xg_dba/internal/middleware/utils"
 	"xg_dba/internal/models"
 
-	"github.com/mitchellh/mapstructure"
 	"github.com/spf13/viper"
 )
 
+type ServerDbCache struct {
+	Cache map[string]models.Cache
+}
+
 // InitCache 初始化缓存,从指定目录中读取 TOML 文件内容并存储到 packageModels 映射中。
-func InitCache(folderPath string) {
-	packageModels := make(map[string]models.Cache)
+func InitCache(folderPath string) (*ServerDbCache, error) {
+	serverDb := ServerDbCache{}
+	serverDb.Cache = make(map[string]models.Cache)
 
-	err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
+	err := filepath.WalkDir(folderPath, func(path string, d fs.DirEntry, err error) error {
 		if err != nil {
 			return err
 		}
 
-		if info.IsDir() {
-			listenFilePath := filepath.Join(path, "listen.toml")
+		if d.IsDir() {
+			//包含一组集群信息
+			cacheTemp := models.Cache{}
+			//一组集群的连接信息
+			connects := make(map[string]models.ConnectInfo)
+			//一组集群的系统元信息
+			systemMeta := make(map[string]models.SystemMetaInfo)
 
-			var connectInfo models.ConnectInfo
-			var systemMetaInfo models.SystemMetaInfo
-
-			// 如果 listen.toml 文件存在,解析它到 connectInfo 结构体中
-			if fileExists(listenFilePath) {
-				if err := parseTomlFile(listenFilePath, &connectInfo); err != nil {
-					return err
-				}
+			// 如果 listen.toml 文件存在,解析它到 connectInfoMap 结构体中
+			listenFilePath := filepath.Join(path, "listen.toml")
+			if utils.FileExists(listenFilePath) {
+				//解析 listen.toml 文件
+				parseListen(listenFilePath, connects)
+				cacheTemp.Connect = connects
 			}
 
-			// 获取目录中最新的 os_info.toml 文件
-			latestOsInfoFilePath, err := getLatestOsInfoFile(path)
+			/* 获取目录中最新的 os_info.toml 文件
+			这里会有多个节点的 os_info.toml 文件,需要找到每个节点最新的一个*/
+			systemMeta, err = utils.GetLatestOsInfoFiles(path)
 			if err != nil {
-				return err
+				return fmt.Errorf("获取目录中最新的 os_info.toml 文件时出错: %v", err)
 			}
-
 			// 如果找到最新的 os_info.toml 文件,解析它到 systemMetaInfo 结构体中
-			if latestOsInfoFilePath != "" {
-				if err := parseTomlFile(latestOsInfoFilePath, &systemMetaInfo); err != nil {
-					return err
+			// if latestOsInfoFilePath != "" {
+			// 	var systemMetaInfo models.SystemMetaInfo
+			// 	if err := utils.ParseTomlFile(latestOsInfoFilePath, &systemMetaInfo); err != nil {
+			// 		return err
+			// 	}
+
+			// 	fmt.Println("获取目录中最新的 os_info.toml 文件:", systemMetaInfo)
+
+			// }
+
+			if cacheTemp.Connect != nil {
+				// 获取 listen.toml 文件所在的文件夹名,父文件名字
+				folderName := filepath.Base(path)
+				cacheTemp.FileInfo.BaseFileName = folderName
+				//操作系统环境信息
+				if systemMeta != nil {
+					cacheTemp.System = systemMeta
 				}
-			}
 
-			// 使用主机地址作为键,将解析的信息存储到 packageModels 映射中
-			host := connectInfo.Ssh.Host
-			if host != "" {
-				packageModels[host] = models.Cache{
-					ConnectInfo:    connectInfo,
-					SystemMetaInfo: systemMetaInfo,
-				}
+				//将一组集群放入缓存中
+				serverDb.Cache[folderName] = cacheTemp
+
 			}
+
 		}
 		return nil
 	})
-
 	if err != nil {
-		fmt.Printf("遍历路径 %q 时出错: %v\n", folderPath, err)
-		return
+		return nil, fmt.Errorf("遍历路径 %q 时出错: %v", folderPath, err)
 	}
 
 	// 打印每个主机的缓存数据
-	for host, cache := range packageModels {
-		fmt.Printf("主机: %s, 缓存: %+v\n", host, cache)
-	}
-}
-
-// fileExists 检查给定路径的文件是否存在。
-func fileExists(filename string) bool {
-	info, err := os.Stat(filename)
-	if os.IsNotExist(err) {
-		return false
-	}
-	return !info.IsDir()
+	// for key, connectInfo := range serverDb.Cache {
+	// 	fmt.Printf("主机: %s, 缓存: %+v ,父文件: %s\n", key, connectInfo.Connect, connectInfo.FileInfo.BaseFileName)
+	// }
+	return &serverDb, nil
 }
-
-// parseTomlFile 使用 viper 解析 TOML 文件到提供的结构体中。
-func parseTomlFile(filePath string, v interface{}) error {
-	// 为了防止 viper 的全局状态干扰解析结果,使用新的 viper 实例
+func parseListen(listenFilePath string, config map[string]models.ConnectInfo) {
+	// 设置配置文件名和路径
 	vp := viper.New()
-	vp.SetConfigFile(filePath)
+	vp.SetConfigFile(listenFilePath)
 	vp.SetConfigType("toml")
 
 	if err := vp.ReadInConfig(); err != nil {
-		return err
+		panic(fmt.Errorf("parseListen fatal error reading config file: %w", err))
 	}
 
-	// 使用 `vp.AllSettings()` 将配置映射到提供的结构体
-	settings := vp.AllSettings()
-	if err := decodeSettings(settings, v); err != nil {
-		return err
+	// 遍历读取 server_db 中的各个配置
+	serverDBs := vp.GetStringMap("server_db")
+	for key := range serverDBs {
+		dbConfig := vp.Sub(fmt.Sprintf("server_db.%s", key))
+		if dbConfig == nil {
+			continue
+		}
+
+		// 创建结构体实例并存储到 config 中
+		config[key] = models.ConnectInfo{
+			Ssh: models.SshInfo{
+				Username: dbConfig.GetString("ssh_username"),
+				Password: dbConfig.GetString("ssh_password"),
+				Host:     dbConfig.GetString("ssh_ip"),
+				Port:     dbConfig.GetString("ssh_port"),
+			},
+			Db: models.DbInfo{
+				User:     dbConfig.GetString("db_user"),
+				Password: dbConfig.GetString("db_password"),
+				Database: dbConfig.GetString("db_database"),
+				Port:     dbConfig.GetString("db_port"),
+			},
+		}
 	}
 
-	return nil
 }
 
-// decodeSettings 将 viper 的设置映射解码到目标结构体。
-func decodeSettings(settings map[string]interface{}, v interface{}) error {
-	decoderConfig := &mapstructure.DecoderConfig{
-		Result:           v,
-		TagName:          "toml",
-		WeaklyTypedInput: true,
-	}
-	decoder, err := mapstructure.NewDecoder(decoderConfig)
+// 刷新缓存
+
+func (cache *ServerDbCache) RefreshCache() error {
+	var err error
+	cache, err = InitCache("./config")
 	if err != nil {
 		return err
 	}
-	return decoder.Decode(settings)
+	return nil
 }
 
-// getLatestOsInfoFile 在给定目录中查找最新的 os_info TOML 文件。
-// 它查找匹配模式 "os_info_<timestamp>.toml" 的文件,并返回最新的一个。
-func getLatestOsInfoFile(dirPath string) (string, error) {
-	pattern := regexp.MustCompile(`^os_info(?:_\d{8}_\d{6})?\.toml$`)
-	files, err := os.ReadDir(dirPath)
-	if err != nil {
-		return "", err
-	}
+func (cache *ServerDbCache) LoadCache(folderPath string) error {
+	return nil
+}
 
-	var matchingFiles []string
-	for _, file := range files {
-		if !file.IsDir() && pattern.MatchString(file.Name()) {
-			matchingFiles = append(matchingFiles, filepath.Join(dirPath, file.Name()))
+func (cache *ServerDbCache) GetConnectCache() (map[string]map[string]api.ConnectInfoResopnse, error) {
+
+	// 获取所有 Connect 数据
+	// allConnects := make(map[string]map[string]models.ConnectInfo)
+	// for group, cache := range cache.Cache {
+	// 	allConnects[group] = cache.Connect
+	// }
+	allConnects := make(map[string]map[string]api.ConnectInfoResopnse)
+
+	for group, cacheV := range cache.Cache {
+
+		TempMap := make(map[string]api.ConnectInfoResopnse)
+		connInfoMap := make(map[string]api.ConnectInfo)
+
+		for key, cache := range cacheV.Connect {
+			//fmt.Printf("group: %s, key: %s, cachhe: %#v \n", group, key, cachhe)
+			connInfoMap[key] = api.SetConnectInfo(cache.Ssh.Username, cache.Ssh.Password, cache.Ssh.Host, cache.Ssh.Port, cache.Db.User, cache.Db.Password, cache.Db.Database, cache.Db.Port)
 		}
-	}
+		//cacheV.FileInfo.BaseFileName
+		connRes := api.ConnectInfoResopnse{
+			ConnInfo:     connInfoMap,
+			BaseFileName: cacheV.FileInfo.BaseFileName,
+		}
+		TempMap[group] = connRes
+		//	TempMap[group].ConnInfo = connInfoMap
+		//fmt.Println("父文件夹名字 :", cacheV.FileInfo.BaseFileName, conncetInfoTemp.BaseFileName)
+		allConnects[group] = TempMap
 
-	// 如果没有找到匹配的文件,返回空字符串
-	if len(matchingFiles) == 0 {
-		return "", nil
 	}
 
-	// 将匹配的文件按降序排序,以获取最新的文件
-	sort.Slice(matchingFiles, func(i, j int) bool {
-		return matchingFiles[i] > matchingFiles[j]
-	})
+	return allConnects, nil
+}
+
+// 获取指定服务器信息
+func (cache *ServerDbCache) GetSingleSystemInfoCache(BaseFileName, ip string) (models.SystemMetaInfo, error) {
 
-	return matchingFiles[0], nil
+	info := cache.Cache[BaseFileName].System[ip]
+	fmt.Println("单个系统的信息info:", info)
+	return info, nil
 }

+ 3 - 1
internal/middleware/test/cache_test.go

@@ -9,5 +9,7 @@ func TestCache(t *testing.T) {
 
 	// 指定需要查找的根文件夹路径
 	folderPath := "C:\\Program_GT\\Code\\Go\\Work\\xugu\\xg_dba\\config"
-	middleware.InitCache(folderPath)
+	cache, _ := middleware.InitCache(folderPath)
+	cache.GetConnectCache()
+	//fmt.Println(cache)
 }

+ 119 - 0
internal/middleware/utils/utils.go

@@ -0,0 +1,119 @@
+package utils
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"regexp"
+	"xg_dba/internal/models"
+
+	"github.com/mitchellh/mapstructure"
+	"github.com/spf13/viper"
+)
+
+// fileExists 检查给定路径的文件是否存在。
+func FileExists(filename string) bool {
+	info, err := os.Stat(filename)
+	if os.IsNotExist(err) {
+		return false
+	}
+	return !info.IsDir()
+}
+
+// parseTomlFile 使用 viper 解析 TOML 文件到提供的结构体中。
+func ParseTomlFile1(filePath string, v interface{}) error {
+	// 为了防止 viper 的全局状态干扰解析结果,使用新的 viper 实例
+	vp := viper.New()
+	vp.SetConfigFile(filePath)
+	vp.SetConfigType("toml")
+
+	if err := vp.ReadInConfig(); err != nil {
+		return err
+	}
+
+	// 使用 `vp.AllSettings()` 将配置映射到提供的结构体
+	settings := vp.AllSettings()
+	if err := DecodeSettings(settings, v); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// parseTomlFile 使用 viper 解析 TOML 文件到提供的结构体中。
+func ParseTomlFile(filePath string, v interface{}) error {
+	// 创建一个新的 viper 实例,防止全局状态干扰
+	vp := viper.New()
+	vp.SetConfigFile(filePath)
+	vp.SetConfigType("toml")
+
+	// 读取配置文件
+	if err := vp.ReadInConfig(); err != nil {
+		return err
+	}
+
+	// 将配置文件内容解析到提供的结构体
+	if err := vp.Unmarshal(v); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// decodeSettings 将 viper 的设置映射解码到目标结构体。
+func DecodeSettings(settings map[string]interface{}, v interface{}) error {
+	decoderConfig := &mapstructure.DecoderConfig{
+		Result:           v,
+		TagName:          "toml",
+		WeaklyTypedInput: true,
+	}
+	decoder, err := mapstructure.NewDecoder(decoderConfig)
+	if err != nil {
+		return err
+	}
+	return decoder.Decode(settings)
+}
+
+// getLatestOsInfoFile 在给定目录中查找最新的 os_info TOML 文件。
+// 它查找匹配模式 "os_info_<timestamp>.toml" 的文件,并返回最新的一个。
+func GetLatestOsInfoFiles(dirPath string) (map[string]models.SystemMetaInfo, error) {
+	// 编译正则表达式,匹配并捕获 <ip> 和 <timestamp>
+	pattern := regexp.MustCompile(`^(\d{1,3}(?:\.\d{1,3}){3})_os_info_(\d{8}_\d{6})\.toml$`)
+	files, err := os.ReadDir(dirPath)
+	if err != nil {
+		return nil, err
+	}
+
+	// 创建用于存储最新文件的映射
+	latestFiles := make(map[string]string)
+	latestTimestamps := make(map[string]string)
+
+	for _, file := range files {
+		if !file.IsDir() {
+			name := file.Name()
+			matches := pattern.FindStringSubmatch(name)
+			if len(matches) == 3 {
+				ip := matches[1]
+				timestamp := matches[2]
+				// 比较并更新最新的时间戳
+				if prevTimestamp, exists := latestTimestamps[ip]; !exists || timestamp > prevTimestamp {
+					latestTimestamps[ip] = timestamp
+					latestFiles[ip] = filepath.Join(dirPath, name)
+				}
+			}
+		}
+	}
+	
+	systemMeta := make(map[string]models.SystemMetaInfo)
+	for ip, path := range latestFiles {
+		fmt.Printf("Latest os_info file for %s: %s\n", ip, path)
+		var systemMetaInfo models.SystemMetaInfo
+		if err := ParseTomlFile(path, &systemMetaInfo); err != nil {
+			return nil, err
+		}
+		systemMeta[ip] = systemMetaInfo
+		fmt.Println("获取目录中最新的 os_info.toml 文件:", systemMetaInfo)
+	}
+
+	return systemMeta, nil
+}

+ 12 - 6
internal/models/cache_models.go

@@ -1,15 +1,21 @@
 package models
 
 type Cache struct {
-	ConnectInfo
-	SystemMetaInfo
+	FileInfo FileMetaInfo
+	Connect  map[string]ConnectInfo
+	System   map[string]SystemMetaInfo
 }
 
-// 定义结构体
+// 文件存储信息
+type FileMetaInfo struct {
+	BaseFileName string
+}
+
+// 连接信息
 type ConnectInfo struct {
-	Id  string  `json:"id" toml:"id"`
-	Ssh SshInfo `json:"ssh_info" toml:"server"`
-	Db  DbInfo  `json:"db_info" toml:"db"`
+	//	Id  string  `json:"id" toml:"id"`
+	Ssh SshInfo `json:"ssh_info" toml:"db"`
+	Db  DbInfo  `json:"db_info" toml:"ssh"`
 }
 
 type SshInfo struct {

+ 1 - 0
internal/models/system_models.go

@@ -8,4 +8,5 @@ type SystemMetaInfo struct {
 	DiskSpeed    string `toml:"disk_speed"`
 	Network      string `toml:"network"`
 	NetworkSpeed string `toml:"network_speed"`
+	Error        string `toml:"error"`
 }

+ 57 - 0
internal/router.go

@@ -0,0 +1,57 @@
+package internal
+
+import (
+	"fmt"
+	"xg_dba/internal/controllers/connect"
+	"xg_dba/internal/controllers/system"
+	"xg_dba/internal/global"
+
+	"github.com/gin-gonic/gin"
+)
+
+// SetupRouter 配置Gin路由
+func SetupRouter() *gin.Engine {
+	r := gin.Default()
+	// 静态文件服务配置
+	configureStaticFileServing(r)
+	configureRootHandler(r)
+	public := r.Group("/api")
+	{
+		//public.POST("/register", connect.SaveConnectInfo_controller)
+
+		//连接信息 (服务器,数据库)
+		public.GET("/GetConnectInfo", connect.GetConnectInfo_controller)
+		public.POST("/AddConnectInfo", connect.AddConnectInfo_controller)
+
+		//服务器信息
+		public.POST("/GetSingleSystemInfo", system.GetSingleSystemInfo_controller)
+		public.POST("/SetSystemInfo", system.SetSystemInfo_controller)
+		public.GET("/GetSystemInfoProgress", system.GetSystemInfoProgress_controller)
+	}
+	return r
+}
+
+// HttpController 启动 HTTP 服务器
+func HttpController() {
+	r := SetupRouter()
+	ipPort := fmt.Sprintf("%s:%s", global.Cfg.App.Ip, global.Cfg.App.Port)
+	fmt.Println("ipPort:", ipPort)
+	err := r.Run(ipPort)
+	if err != nil {
+		panic(err)
+	}
+	global.Logger.Info("Server is running on port", fmt.Sprintf("%s:%s", global.Cfg.App.Ip, global.Cfg.App.Port))
+
+}
+
+// configureStaticFileServing 配置静态文件服务
+func configureStaticFileServing(r *gin.Engine) {
+	r.Static("/static", "./static")
+}
+
+// configureRootHandler 配置根路径请求处理
+func configureRootHandler(r *gin.Engine) {
+	r.GET("/", func(c *gin.Context) {
+		c.File("./static/index.html")
+	})
+}

+ 69 - 30
internal/services/connect/connect.go

@@ -1,49 +1,88 @@
 package services
 
 import (
+	"fmt"
 	"log"
+	"os"
+	"path/filepath"
 	"xg_dba/api"
-	"xg_dba/module/remote"
-	"xg_dba/module/toml"
+	"xg_dba/internal/global"
+
+	"github.com/spf13/viper"
 )
 
 // 设置连接信息
-func SetConnectInfo_service(connectInfo api.ConnectInfoRequest, saveFilePath string) error {
-	// 连接测试
-	sshClient, err := remote.NewSSHClient(connectInfo.Id, connectInfo.Ssh.Username, connectInfo.Ssh.Password, connectInfo.Ssh.Host, connectInfo.Ssh.Port)
-	if err != nil {
-		log.Println("连接失败", err)
+func SetConnectInfo_service(connectInfos []api.ConnectInfoRequest, saveFilePath string) error {
+	// 创建目录(如果不存在)
+	dirPath := filepath.Dir(saveFilePath)
+	if err := os.MkdirAll(dirPath, os.ModePerm); err != nil {
+		log.Println("创建目录失败", err)
 		return err
 	}
-	defer sshClient.Close()
-	// 将connectInfo存储到本地
-	//localPath := "./config/" + connectInfo.Ssh.Host + "/" + connectInfo.Ssh.Host + ".toml"
-	if err := toml.CreateFileIfNotExists(saveFilePath); err != nil {
-		log.Println("创建文件失败", err)
-		return err
+
+	// 创建空的配置文件(如果文件不存在)
+	if _, err := os.Stat(saveFilePath); os.IsNotExist(err) {
+		file, err := os.Create(saveFilePath)
+		if err != nil {
+			log.Println("创建文件失败", err)
+			return err
+		}
+		defer file.Close()
+	}
+
+	// 设置 Viper 配置文件路径
+	viper.SetConfigFile(saveFilePath)
+	viper.SetConfigType("toml")
+
+	// 读取配置文件(忽略不存在的文件错误)
+	if err := viper.ReadInConfig(); err != nil {
+		if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
+			log.Println("读取配置文件失败", err)
+			return err
+		}
 	}
 
-	// 定义组和默认值
-	groups := map[string]interface{}{
-		"server": map[string]interface{}{
-			"Ssh_Ip":       connectInfo.Ssh.Host,
-			"Ssh_Port":     connectInfo.Ssh.Port,
-			"Ssh_Username": connectInfo.Ssh.Username,
-			"Ssh_Password": connectInfo.Ssh.Password,
-		},
-		"db": map[string]interface{}{
-			"Db_Port":     connectInfo.Db.Port,
-			"Db_User":     connectInfo.Db.User,
-			"Db_Password": connectInfo.Db.Password,
-			"Db_Database": connectInfo.Db.Database,
-		},
-		"system": map[string]interface{}{},
+	// 遍历每个连接信息
+	for idx, connectInfo := range connectInfos {
+		// 连接测试
+		// sshClient, err := remote.NewSSHClient(connectInfo.Id, connectInfo.Ssh.Username, connectInfo.Ssh.Password, connectInfo.Ssh.Host, connectInfo.Ssh.Port)
+		// if err != nil {
+		// 	log.Println("连接失败", err)
+		// 	return err
+		// }
+		// defer sshClient.Close()
+
+		// 定义组和默认值
+		group := map[string]interface{}{
+			"db_database":  connectInfo.Db.Database,
+			"db_password":  connectInfo.Db.Password,
+			"db_port":      connectInfo.Db.Port,
+			"db_user":      connectInfo.Db.User,
+			"ssh_ip":       connectInfo.Ssh.Host,
+			"ssh_password": connectInfo.Ssh.Password,
+			"ssh_port":     connectInfo.Ssh.Port,
+			"ssh_username": connectInfo.Ssh.Username,
+		}
+
+		// 将组写入 Viper 配置,使用 server_db.idx 的方式
+		for key, value := range group {
+			viper.Set(fmt.Sprintf("server_db.%d.%s", idx+1, key), value)
+		}
+	}
+
+	// 保存到文件
+	if err := viper.WriteConfigAs(saveFilePath); err != nil {
+		log.Println("写入配置文件失败", err)
+		return err
 	}
 
-	if err := toml.CreateTOMLGoups(saveFilePath, groups); err != nil {
-		log.Println("创建文件失败", err)
+	//更新缓存
+	if err := global.Cache.RefreshCache(); err != nil {
+		log.Println("写入缓存失败", err)
 		return err
 	}
 
+	fmt.Println("缓存刷新: ", global.Cache.Cache)
+
 	return nil
 }

+ 90 - 46
internal/services/system/system_services.go

@@ -2,6 +2,7 @@ package services
 
 import (
 	"bytes"
+	"database/sql"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -11,19 +12,24 @@ import (
 	"xg_dba/internal/global"
 	"xg_dba/internal/models"
 	"xg_dba/module/remote"
+	_ "xg_dba/pkg/xugu"
 
 	"github.com/BurntSushi/toml"
 )
 
-func SetSystemInfo_service(connectInfo api.ConnectInfoRequest, saveFilePath string) error {
+func SetSystemInfo_service(connectInfo []api.ServerInfoRequest, saveFilePath string, progressID string) error {
+	//ping服务器是否能连通
 	//ssh连接服务器
 	//执行命令
 	//返回结果并存储到本地
 	var wg sync.WaitGroup
 	var mu sync.Mutex
+	defer global.Progress.CloseProgress(progressID) // 处理完成后关闭进度 channel
 
-	systemInfo := models.SystemMetaInfo{}
-
+	progressChan, ok := global.Progress.GetProgressChan(progressID)
+	if !ok {
+		return fmt.Errorf("Progress 队列错误")
+	}
 	commands := map[string]string{
 		"OS Info":       "cat /etc/os-release",
 		"CPU Info":      "lscpu",
@@ -34,61 +40,90 @@ func SetSystemInfo_service(connectInfo api.ConnectInfoRequest, saveFilePath stri
 		"Network Speed": "ping -c 4 8.8.8.8",
 	}
 
-	for name, cmd := range commands {
-		wg.Add(1)
+	//循环每一组集群去执行命令1
+	for _, server := range connectInfo {
 
-		go func(name, cmd string) {
+		wg.Add(1)
+		//开启协程对每一组集群去执行命令
+		go func(server api.ServerInfoRequest) {
 			defer wg.Done()
 
-			// 每次执行命令都新建一个 SSH 客户端
-			sshClient, err := remote.NewSSHClient(connectInfo.Id, connectInfo.Ssh.Username, connectInfo.Ssh.Password, connectInfo.Ssh.Host, connectInfo.Ssh.Port)
-			if err != nil {
-				global.Logger.Errorf("NewSSHClient failed for %s: %v", name, err)
-				return
-			}
-			defer sshClient.Close()
+			systemInfo := models.SystemMetaInfo{}
+			var serverWg sync.WaitGroup
 
-			output, err := sshClient.RunCommand(cmd)
-			if err != nil {
-				global.Logger.Errorf("%s command failed: %v", name, err)
-				return
+			for name, cmd := range commands {
+				serverWg.Add(1)
+
+				go func(name, cmd string, server api.ServerInfoRequest) {
+					defer serverWg.Done()
+
+					// 每次执行命令都新建一个 SSH 客户端
+					sshClient, err := remote.NewSSHClient("1", server.Username, server.Password, server.Host, server.Port)
+					if err != nil {
+						global.Logger.Errorf("NewSSHClient failed for %s on server %s: %v", name, server.Host, err)
+						progressChan <- fmt.Sprintf("%s on server %s: failed to connect", name, server.Host)
+						return
+					}
+					defer sshClient.Close()
+
+					output, err := sshClient.RunCommand(cmd)
+					if err != nil {
+						global.Logger.Errorf("%s command failed on server %s: %v", name, server.Host, err)
+						progressChan <- fmt.Sprintf("%s on server %s: command failed", name, server.Host)
+						return
+					}
+
+					mu.Lock()
+					// 根据命令名称将输出赋值到结构体字段
+					switch name {
+					case "OS Info":
+						systemInfo.Os = output
+					case "CPU Info":
+						systemInfo.Cpu = output
+					case "Memory Info":
+						systemInfo.Memory = output
+					case "Disk Info":
+						systemInfo.Disk = output
+					case "Network Info":
+						systemInfo.Network = output
+					case "Disk Speed":
+						systemInfo.DiskSpeed = output
+					case "Network Speed":
+						systemInfo.NetworkSpeed = output
+					}
+					mu.Unlock()
+
+					progressChan <- fmt.Sprintf("%s on server %s: completed. data =  %s", name, server.Host, output)
+					fmt.Printf("--%s on server %s:\n%s\n", name, server.Host, output)
+				}(name, cmd, server)
 			}
 
-			mu.Lock()
-			// 根据命令名称将输出赋值到结构体字段
-			switch name {
-			case "OS Info":
-				systemInfo.Os = output
-			case "CPU Info":
-				systemInfo.Cpu = output
-			case "Memory Info":
-				systemInfo.Memory = output
-			case "Disk Info":
-				systemInfo.Disk = output
-			case "Network Info":
-				systemInfo.Network = output
-			case "Disk Speed":
-				systemInfo.DiskSpeed = output
-			case "Network Speed":
-				systemInfo.NetworkSpeed = output
+			serverWg.Wait()
+
+			if systemInfo.Cpu != "" {
+				//progressChan <- fmt.Sprintf("Error: failed to get system meta info for server %s", server.Host)
+				timestamp := time.Now().Format("20060102_150405")
+				os.MkdirAll(filepath.Dir(saveFilePath), os.ModePerm)
+				filePath := fmt.Sprintf("%s%s_os_info_%s.toml", saveFilePath, server.Host, timestamp)
+				err := saveSystemMetaInfoToFile(systemInfo, filePath)
+				if err != nil {
+					progressChan <- fmt.Sprintf("Error: failed to save system meta info for server %s", server.Host)
+					fmt.Printf("Error: %v\n", err)
+				} else {
+					progressChan <- fmt.Sprintf("System meta info saved successfully for server %s", server.Host)
+					fmt.Println("System meta info saved successfully for server", server.Host)
+				}
+				return
+			} else {
+				return
 			}
-			mu.Unlock()
 
-			fmt.Printf("--%s:\n%s\n", name, output)
-		}(name, cmd)
+		}(server)
 	}
 
 	wg.Wait()
 
-	timestamp := time.Now().Format("20060102_150405")
-	os.MkdirAll(filepath.Dir(saveFilePath), os.ModePerm)
-	filePath := fmt.Sprintf("%sos_info_%s.toml", saveFilePath, timestamp)
-	err := saveSystemMetaInfoToFile(systemInfo, filePath)
-	if err != nil {
-		fmt.Printf("Error: %v\n", err)
-	} else {
-		fmt.Println("System meta info saved successfully")
-	}
+	//close(progressChan)
 	return nil
 }
 
@@ -106,3 +141,12 @@ func saveSystemMetaInfoToFile(info models.SystemMetaInfo, filePath string) error
 	}
 	return nil
 }
+
+func GetDb() {
+	// 连接数据库
+	db, err := sql.Open("xugusql", "root:123456@tcp(127.0.0.1:3306)/test")
+	if err != nil {
+		panic(err.Error())
+	}
+	db.Close()
+}

+ 46 - 31
internal/services/test/connect_test.go

@@ -1,47 +1,62 @@
 package test
 
 import (
+	"fmt"
 	"testing"
 	"xg_dba/api"
+	"xg_dba/internal/global"
 	connect "xg_dba/internal/services/connect"
-	system "xg_dba/internal/services/system"
 )
 
-func TestConnectInfo(t *testing.T) {
-	connectInfo := api.ConnectInfoRequest{
-		Id: "1",
-		Ssh: api.SshInfo{
-			Username: "gtong",
-			Password: "845895",
-			Host:     "127.0.0.1",
-			Port:     "22",
-		},
-		Db: api.DbInfo{
-			User:     "SYSDBA",
-			Password: "SYSDBA",
-			Port:     "5236",
-			Database: "test",
-		},
-	}
-	localPath := "C:\\Program_GT\\Code\\Go\\Work\\xugu\\xg_dba\\config/" + connectInfo.Ssh.Host + "/" + connectInfo.Ssh.Host + ".toml"
-	if err := connect.SetConnectInfo_service(connectInfo, localPath); err != nil {
-		t.Errorf("SetConnectInfo_service failed: %v", err)
-	}
+func TestMain(m *testing.M) {
+	// 初始化 Logger,指向一个缓冲区或其他目的地以便测试
+	//Logger := logger.InitLogs("C:\\Program_GT\\Code\\Go\\Work\\xugu\\xg_dba\\logs\\test", "info")
+	global.GlobalInit("C:\\Program_GT\\Code\\Go\\Work\\xugu\\xg_dba\\config")
+	// 运行所有测试
+	exitVal := m.Run()
+	fmt.Println("exitVal:", exitVal)
+	// 进行必要的清理工作(如果有)
+	// ...
 
+	// 退出
+	//os.Exit(exitVal)
 }
 
-func TestGetServerInfo(t *testing.T) {
-	saveFilePath := "C:\\Program_GT\\Code\\Go\\Work\\xugu\\xg_dba\\config\\test\\"
-	connectInfo := api.ConnectInfoRequest{
-		Id: "1",
-		Ssh: api.SshInfo{
-			Username: "gtong",
-			Password: "845895",
-			Host:     "127.0.0.1",
-			Port:     "22",
+func TestConnectInfo(t *testing.T) {
+	connectInfo := []api.ConnectInfoRequest{
+		api.ConnectInfoRequest{
+			//	Id: "1",
+			Ssh: api.SshInfo{
+				Username: "gtong",
+				Password: "845895",
+				Host:     "327.0.0.1",
+				Port:     "22",
+			},
+			Db: api.DbInfo{
+				User:     "SYSDBA",
+				Password: "SYSDBA",
+				Port:     "5236",
+				Database: "test",
+			},
+		},
+		api.ConnectInfoRequest{
+			//Id: "2",
+			Ssh: api.SshInfo{
+				Username: "gtong",
+				Password: "845895",
+				Host:     "327.0.0.2",
+				Port:     "22",
+			},
+			Db: api.DbInfo{
+				User:     "SYSDBA",
+				Password: "SYSDBA",
+				Port:     "5231",
+				Database: "test",
+			},
 		},
 	}
-	if err := system.SetSystemInfo_service(connectInfo, saveFilePath); err != nil {
+	localPath := "C:\\Program_GT\\Code\\Go\\Work\\xugu\\xg_dba\\config/" + connectInfo[0].Ssh.Host + "/" + connectInfo[0].Ssh.Host + ".toml"
+	if err := connect.SetConnectInfo_service(connectInfo, localPath); err != nil {
 		t.Errorf("SetConnectInfo_service failed: %v", err)
 	}
 

+ 6 - 0
internal/services/test/dba.toml

@@ -0,0 +1,6 @@
+[config]
+ip = "127.0.0.1"
+port = "8080"
+
+[logs]
+app_log = "./logs"

+ 0 - 0
internal/services/test/logs/log-2024-10-21.log


+ 14 - 0
internal/services/test/logs/log-2024-10-22.log

@@ -0,0 +1,14 @@
+{"fields.file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1","level":"error","msg":"NewSSHClient failed for CPU Info: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T15:39:43+08:00"}
+{"fields.file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1","level":"error","msg":"NewSSHClient failed for Network Info: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T15:39:43+08:00"}
+{"fields.file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1","level":"error","msg":"NewSSHClient failed for Memory Info: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T15:39:43+08:00"}
+{"fields.file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1","level":"error","msg":"NewSSHClient failed for Network Speed: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T15:39:43+08:00"}
+{"fields.file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1","level":"error","msg":"NewSSHClient failed for Disk Info: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T15:39:43+08:00"}
+{"fields.file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1","level":"error","msg":"NewSSHClient failed for Disk Speed: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T15:39:43+08:00"}
+{"fields.file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1","level":"error","msg":"NewSSHClient failed for OS Info: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T15:39:43+08:00"}
+{"fields.file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1","level":"error","msg":"NewSSHClient failed for Network Speed: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T16:26:37+08:00"}
+{"fields.file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1","level":"error","msg":"NewSSHClient failed for Disk Info: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T16:26:37+08:00"}
+{"fields.file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1","level":"error","msg":"NewSSHClient failed for CPU Info: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T16:26:37+08:00"}
+{"fields.file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1","level":"error","msg":"NewSSHClient failed for Network Info: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T16:26:37+08:00"}
+{"fields.file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1","level":"error","msg":"NewSSHClient failed for OS Info: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T16:26:37+08:00"}
+{"fields.file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1","level":"error","msg":"NewSSHClient failed for Disk Speed: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T16:26:37+08:00"}
+{"fields.file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","file":"c:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:46","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1","level":"error","msg":"NewSSHClient failed for Memory Info: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T16:26:37+08:00"}

+ 0 - 0
internal/services/test/logs/log-2024-10-25.log


+ 49 - 0
internal/services/test/system_test.go

@@ -0,0 +1,49 @@
+package test
+
+import (
+	"fmt"
+	"testing"
+	"xg_dba/api"
+	"xg_dba/internal/global"
+	services "xg_dba/internal/services/system"
+	_ "xg_dba/pkg/xugu"
+)
+
+func TestGetServerInfo(t *testing.T) {
+	saveFilePath := "C:\\Program_GT\\Code\\Go\\Work\\xugu\\xg_dba\\config\\test\\"
+	connectInfo := api.ServerInfoRequest{
+		//	Id: "1",
+
+		Username: "gtong",
+		Password: "845895",
+		Host:     "127.0.0.1",
+		Port:     "22",
+	}
+	connectInfo2 := api.ServerInfoRequest{
+		//	Id: "1",
+
+		Username: "root",
+		Password: "cdyanfa@2024",
+		Host:     "10.28.25.113",
+		Port:     "22",
+	}
+
+	progressChan := make(chan string)
+	go func() {
+		for progress := range progressChan {
+			fmt.Println("Progress:", progress)
+		}
+	}()
+	progressID := global.Progress.CreateProgress()
+	if err := services.SetSystemInfo_service([]api.ServerInfoRequest{
+		connectInfo,
+		connectInfo2,
+	}, saveFilePath, progressID); err != nil {
+		t.Errorf("SetConnectInfo_service failed: %v", err)
+	}
+
+}
+
+func TestGeDb(t *testing.T) {
+	services.GetDb()
+}

+ 4 - 0
internal/services/xugu_serices/selcet.go

@@ -0,0 +1,4 @@
+package xugu_serice
+
+
+

+ 0 - 0
logs/log-2024-10-18.log


+ 0 - 0
logs/log-2024-10-21.log


+ 28 - 0
logs/log-2024-10-22.log

@@ -0,0 +1,28 @@
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Memory Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T17:57:24+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for OS Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T17:57:24+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Network Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T17:57:24+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for CPU Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T17:57:24+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Network Speed on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T17:57:24+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Disk Speed on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T17:57:24+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Disk Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T17:57:24+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Network Speed on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T18:06:16+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Disk Speed on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T18:06:16+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Disk Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T18:06:16+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Network Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T18:06:16+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for OS Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T18:06:16+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for CPU Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T18:06:16+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Memory Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T18:06:16+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for OS Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T18:06:22+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Network Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T18:06:22+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Memory Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T18:06:22+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for CPU Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T18:06:22+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Network Speed on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T18:06:22+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Disk Info on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T18:06:22+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Disk Speed on server 12.56.23.6: 无法连接到服务器 12.56.23.6: dial tcp 12.56.23.6:22: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.","time":"2024-10-22T18:06:22+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Memory Info on server 185.3.6.1: 无法连接到服务器 185.3.6.1: dial tcp 185.3.6.1:11: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T18:07:25+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for CPU Info on server 185.3.6.1: 无法连接到服务器 185.3.6.1: dial tcp 185.3.6.1:11: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T18:07:25+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for OS Info on server 185.3.6.1: 无法连接到服务器 185.3.6.1: dial tcp 185.3.6.1:11: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T18:07:25+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Disk Info on server 185.3.6.1: 无法连接到服务器 185.3.6.1: dial tcp 185.3.6.1:11: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T18:07:25+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Network Info on server 185.3.6.1: 无法连接到服务器 185.3.6.1: dial tcp 185.3.6.1:11: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T18:07:25+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Disk Speed on server 185.3.6.1: 无法连接到服务器 185.3.6.1: dial tcp 185.3.6.1:11: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T18:07:25+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:52","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Network Speed on server 185.3.6.1: 无法连接到服务器 185.3.6.1: dial tcp 185.3.6.1:11: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-22T18:07:25+08:00"}

+ 14 - 0
logs/log-2024-10-23.log

@@ -0,0 +1,14 @@
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:57","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:57","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Disk Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-23T10:22:47+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:57","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:57","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Disk Speed on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-23T10:22:47+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:57","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:57","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Memory Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-23T10:22:47+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:57","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:57","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for OS Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-23T10:22:47+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:57","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:57","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Network Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-23T10:22:47+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:57","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:57","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Network Speed on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-23T10:22:47+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:57","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:57","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for CPU Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-23T10:22:47+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Network Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-23T19:07:37+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for CPU Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-23T19:07:37+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Network Speed on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-23T19:07:37+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Disk Speed on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-23T19:07:37+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Memory Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-23T19:07:37+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Disk Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-23T19:07:37+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for OS Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-23T19:07:37+08:00"}

+ 7 - 0
logs/log-2024-10-24.log

@@ -0,0 +1,7 @@
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for CPU Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-24T16:10:44+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Disk Speed on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-24T16:10:44+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Memory Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-24T16:10:44+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Disk Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-24T16:10:44+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Network Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-24T16:10:44+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for OS Info on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-24T16:10:44+08:00"}
+{"fields.file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","file":"C:/Program_GT/Code/Go/Work/xugu/xg_dba/internal/services/system/system_services.go:58","func":"xg_dba/internal/services/system.SetSystemInfo_service.func1.1","level":"error","msg":"NewSSHClient failed for Network Speed on server 127.0.0.1: 无法连接到服务器 127.0.0.1: dial tcp 127.0.0.1:22: connectex: No connection could be made because the target machine actively refused it.","time":"2024-10-24T16:10:44+08:00"}

+ 0 - 0
logs/log-2024-10-25.log


+ 6 - 2
main.go

@@ -1,7 +1,11 @@
 package main
 
-import "xg_dba/internal/global"
+import (
+	"xg_dba/internal"
+	"xg_dba/internal/global"
+)
 
 func main() {
-	global.GlobalInit()
+	global.GlobalInit("./config")
+	internal.HttpController()
 }

+ 1 - 1
internal/global/logs.go → module/logger/logs.go

@@ -1,4 +1,4 @@
-package global
+package logger
 
 import (
 	"fmt"

+ 91 - 0
pkg/xugu/xugu_buffer.go

@@ -0,0 +1,91 @@
+package xugu
+
+import (
+	"errors"
+	"net"
+	"time"
+)
+
+var (
+	ErrBusyBuffer = errors.New("busy buffer")
+)
+
+type buffer struct {
+	buf     []byte // buf 是一个字节缓冲区,长度和容量相等。
+	conn    net.Conn
+	idx     int64
+	length  int
+	timeout time.Duration
+}
+
+// newBuffer 分配并返回一个新的缓冲区。
+func newBuffer(nc net.Conn) buffer {
+	return buffer{
+		buf:  make([]byte, 2048),
+		conn: nc,
+	}
+}
+
+func (b *buffer) peekChar() byte {
+	if b.idx > int64(len(b.buf[b.idx:b.length])) {
+		b.readNext(1, false)
+		b.idx-- //peekchar 只查看当前字符,不移动指针,但是readNext会移动指针,所以需要-1
+	}
+	ret := b.buf[b.idx]
+	return ret
+}
+func (b *buffer) reset() {
+	b.idx = 0
+	b.length = 0
+	b.buf = make([]byte, 2048)
+}
+
+func (b *buffer) readNext(need int, reverse bool) ([]byte, error) {
+	if need == 0 {
+		return nil, nil
+	}
+	//长度不够返回
+	if len(b.buf[b.idx:b.length]) < need {
+		buffer := make([]byte, need+1)
+		b.buf = append(b.buf[:b.length], buffer...)
+		n, err := b.conn.Read(b.buf[b.length:])
+		if err != nil {
+			// if err == io.EOF {
+
+			// }
+			return nil, err
+		}
+
+		b.length += n
+
+		for b.length-int(b.idx) < need {
+
+			n, err := b.conn.Read(b.buf[b.length:])
+			if err != nil {
+				// if err == io.EOF {
+
+				// }
+
+				return nil, err
+			}
+			//nTmp += n
+			b.length += n
+		}
+
+	}
+
+	offset := b.idx
+	b.idx += int64(need)
+	if GlobalIsBig {
+		reverse = false
+	}
+	if reverse {
+		tmp := reverseBytes(b.buf[offset:b.idx])
+
+		return tmp, nil
+	} else {
+
+		return b.buf[offset:b.idx], nil
+	}
+
+}

+ 392 - 0
pkg/xugu/xugu_conn.go

@@ -0,0 +1,392 @@
+package xugu
+
+import (
+	"bytes"
+	"context"
+	"database/sql/driver"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"net"
+	"sync"
+)
+
+type xuguConn struct {
+	dsnConfig
+	conn        net.Conn
+	mu          sync.Mutex
+	useSSL      bool // 是否使用加密
+	havePrepare int  //default 0
+
+	prepareNo   int
+	prepareName string
+	//presPrepareCata *Result
+
+	errStr []byte
+
+	sendBuff bytes.Buffer
+	readBuff buffer
+}
+
+type dsnConfig struct {
+	IP             string
+	Port           string
+	Database       string
+	User           string
+	Password       string
+	Encryptor      string //加密库的解密口令
+	CharSet        string //客户端使用的字符集名
+	TimeZone       string
+	IsoLevel       string //事务隔离级别
+	LockTimeout    string //加锁超时
+	AutoCommit     string
+	StrictCommit   string
+	Result         string
+	ReturnSchema   string
+	ReturnCursorID string
+	LobRet         string
+	ReturnRowid    string
+	Version        string
+}
+
+func (xgConn *xuguConn) Begin() (driver.Tx, error) {
+
+	_, err := xgConn.exec("Begin;", nil)
+	if err != nil {
+		return nil, err
+	}
+	return &xuguTx{tconn: xgConn}, nil
+
+}
+
+func (xgConn *xuguConn) Close() error {
+
+	xgConn.mu.Lock()
+	defer xgConn.mu.Unlock()
+	err := xgConn.conn.Close()
+	if err != nil {
+		xgConn.mu.Unlock()
+		return err
+	}
+
+	return nil
+}
+
+func (xgConn *xuguConn) Query(sql string,
+	args []driver.Value) (driver.Rows, error) {
+
+	xgConn.mu.Lock()
+	defer xgConn.mu.Unlock()
+
+	// 检测sql语句不是查询则报错
+	if switchSQLType(sql) != SQL_SELECT {
+		return nil, errors.New("The executed SQL statement is not a SELECT")
+	}
+
+	// 有传进来的参数
+	if len(args) != 0 {
+		values := []xuguValue{}
+		//判断类型
+		for _, param := range args {
+			err := assertParamType(param, &values)
+			if err != nil {
+				return nil, err
+			}
+		}
+		//send msg
+		if err := sockSendPutStatement(xgConn, []byte(sql), &values, len(args)); err != nil {
+			return nil, err
+		}
+
+		if err := sockSendExecute(xgConn); err != nil {
+			return nil, err
+		}
+
+	} else {
+
+		//send msg
+		if err := sockSendPutStatement(xgConn, []byte(sql), nil, len(args)); err != nil {
+			return nil, err
+		}
+
+		if err := sockSendExecute(xgConn); err != nil {
+			return nil, err
+		}
+
+	}
+	//recv msg
+	aR, err := xuguSockRecvMsg(xgConn)
+	if err != nil {
+		return nil, err
+	}
+
+	switch aR.rt {
+	case selectResult:
+
+		rows := &xuguRows{
+			rows_conn: xgConn,
+			aR:        aR,
+			results:   aR.s,
+			colIdx:    0,
+			prepared:  false,
+		}
+
+		return rows, nil
+	case errInfo:
+
+		return nil, errors.New(string(aR.e.ErrStr))
+	case warnInfo:
+
+		return nil, errors.New(string(aR.w.WarnStr))
+	default:
+	}
+
+	return nil, errors.New("xugu Query error")
+}
+
+func (xgConn *xuguConn) Ping(ctx context.Context) error {
+
+	//send
+	xgConn.mu.Lock()
+	defer xgConn.mu.Unlock()
+	sockSendPutStatement(xgConn, []byte("select count(*) from dual;"), nil, 0)
+	sockSendExecute(xgConn)
+
+	_, err := xuguSockRecvMsg(xgConn)
+	if err != nil {
+		return err
+	}
+	xgConn.readBuff.reset()
+
+	return nil
+}
+
+func (xgConn *xuguConn) Prepare(sql string) (driver.Stmt, error) {
+
+	xgConn.mu.Lock()
+	defer xgConn.mu.Unlock()
+
+	//判断sql类型
+	switch switchSQLType(sql) {
+	case SQL_PROCEDURE:
+		return nil, errors.New("Prepare does not support stored procedures")
+	case SQL_UNKNOWN:
+		return nil, errors.New("Unknown SQL statement type")
+	case SQL_CREATE:
+		return nil, errors.New("Prepare does not support DDL.")
+	}
+	//发送创建prepare
+	prepareName := "GTONG"
+
+	err := xuguPrepare(xgConn, sql, prepareName)
+	if err != nil {
+		return nil, err
+	}
+	count := assertParamCount(sql)
+	stmt := &xuguStmt{
+		stmt_conn:  xgConn,
+		prepared:   true,
+		prename:    make([]byte, 128),
+		curopend:   false,
+		curname:    make([]byte, 128),
+		paramCount: count,
+		mysql:      sql,
+	}
+	stmt.prename = []byte(fmt.Sprintf("? %s", xgConn.prepareName))
+
+	return stmt, nil
+}
+
+func xuguPrepare(pConn *xuguConn, cmd_sql string, prepareName string) error {
+
+	prepareName = fmt.Sprintf("%s%d", prepareName, pConn.prepareNo)
+	sqlRet := fmt.Sprintf("PREPARE %s AS %s", prepareName, cmd_sql)
+	pConn.prepareName = prepareName
+	pConn.prepareNo++
+	//send msg
+	sockSendPutStatement(pConn, []byte(sqlRet), nil, 0)
+	sockSendExecute(pConn)
+	//recv msg
+	aR, err := xuguSockRecvMsg(pConn)
+	switch aR.rt {
+
+	case errInfo:
+
+		return errors.New(string(aR.e.ErrStr))
+	case warnInfo:
+
+		return errors.New(string(aR.w.WarnStr))
+	default:
+	}
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func xuguUnPrepare(pConn *xuguConn, prepareName string) error {
+
+	sqlRet := fmt.Sprintf("DEALLOCATE %s ", prepareName)
+
+	//send msg
+	sockSendPutStatement(pConn, []byte(sqlRet), nil, 0)
+	sockSendExecute(pConn)
+	//recv msg
+	aR, err := xuguSockRecvMsg(pConn)
+	switch aR.rt {
+
+	case 'K':
+		return nil
+	case errInfo:
+
+		return errors.New(string(aR.e.ErrStr))
+	case warnInfo:
+
+		return errors.New(string(aR.w.WarnStr))
+	default:
+	}
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (xgConn *xuguConn) Exec(sql string,
+	args []driver.Value) (driver.Result, error) {
+
+	xgConn.mu.Lock()
+	defer xgConn.mu.Unlock()
+
+	// 检测sql语句是查询则报错
+	if switchSQLType(sql) == SQL_SELECT {
+		return nil, errors.New("The executed SQL statement is  a SELECT")
+	}
+
+	// 有传进来的参数
+	if len(args) != 0 {
+		values := []xuguValue{}
+		//判断类型
+		for _, param := range args {
+			err := assertParamType(param, &values)
+			if err != nil {
+				return nil, err
+			}
+		}
+		//send msg
+		if err := sockSendPutStatement(xgConn, []byte(sql), &values, len(args)); err != nil {
+			return nil, err
+		}
+
+		if err := sockSendExecute(xgConn); err != nil {
+			return nil, err
+		}
+
+	} else {
+
+		//send msg
+		if err := sockSendPutStatement(xgConn, []byte(sql), nil, len(args)); err != nil {
+			return nil, err
+		}
+
+		if err := sockSendExecute(xgConn); err != nil {
+			return nil, err
+		}
+
+	}
+	//recv msg
+	aR, err := xuguSockRecvMsg(xgConn)
+	if err != nil {
+		return nil, err
+	}
+
+	switch aR.rt {
+	case selectResult:
+
+		return nil, errors.New("exec is Query error")
+	case errInfo:
+
+		return nil, errors.New(string(aR.e.ErrStr))
+	case warnInfo:
+
+		return nil, errors.New(string(aR.w.WarnStr))
+	case updateResult:
+		return &xuguResult{xgConn: xgConn, affectedRows: int64(aR.u.UpdateNum), insertId: int64(0)}, nil
+	case insertResult:
+
+		return &xuguResult{xgConn: xgConn, affectedRows: int64(aR.i.EffectNum), insertId: int64(binary.LittleEndian.Uint64(aR.i.RowidData))}, nil
+	default:
+		return &xuguResult{
+			xgConn:       xgConn,
+			affectedRows: int64(0),
+			insertId:     int64(0),
+		}, nil
+	}
+
+}
+
+func (xgConn *xuguConn) exec(sql string, args []driver.Value) (driver.Result, error) {
+	// 有传进来的参数
+	if len(args) != 0 {
+		values := []xuguValue{}
+		//判断类型
+		for _, param := range args {
+			err := assertParamType(param, &values)
+			if err != nil {
+				return nil, err
+			}
+		}
+		//send msg
+		if err := sockSendPutStatement(xgConn, []byte(sql), &values, len(args)); err != nil {
+			return nil, err
+		}
+
+		if err := sockSendExecute(xgConn); err != nil {
+			return nil, err
+		}
+
+	} else {
+
+		//send msg
+		if err := sockSendPutStatement(xgConn, []byte(sql), nil, len(args)); err != nil {
+			return nil, err
+		}
+
+		if err := sockSendExecute(xgConn); err != nil {
+			return nil, err
+		}
+
+	}
+
+	//recv msg
+	aR, err := xuguSockRecvMsg(xgConn)
+	if err != nil {
+		return nil, err
+	}
+	switch aR.rt {
+	case selectResult:
+
+		return nil, errors.New("exec is Query error")
+	case updateResult:
+		return &xuguResult{xgConn: xgConn, affectedRows: int64(aR.u.UpdateNum), insertId: int64(0)}, nil
+	case insertResult:
+
+		return &xuguResult{xgConn: xgConn, affectedRows: int64(aR.i.EffectNum), insertId: int64(binary.LittleEndian.Uint64(aR.i.RowidData))}, nil
+	case errInfo:
+
+		return nil, errors.New(string(aR.e.ErrStr))
+	case warnInfo:
+
+		return nil, errors.New(string(aR.w.WarnStr))
+	default:
+		return &xuguResult{
+			xgConn:       xgConn,
+			affectedRows: int64(0),
+			insertId:     int64(0),
+		}, nil
+	}
+
+}

+ 64 - 0
pkg/xugu/xugu_connector.go

@@ -0,0 +1,64 @@
+package xugu
+
+import (
+	"context"
+	"database/sql/driver"
+	"fmt"
+	"net"
+	"time"
+)
+
+type connector struct {
+	dsn string
+}
+
+// Driver implements driver.Connector interface.
+// Driver returns &XuguDriver{}
+func (conntor *connector) Driver() driver.Driver {
+
+	return &XuguDriver{}
+}
+
+// Connect implements driver.Connector interface.
+// Connect returns a connection to the database.
+/*
+dsn解析
+创建连接
+设置为 tcp 长连接(
+创建连接缓冲区
+设置连接超时配置
+接收来自服务端的握手请求
+*/
+func (conntor *connector) Connect(ctx context.Context) (driver.Conn, error) {
+
+	GlobalIsBig = CheckEndian()
+
+	dsnConfig := parseDSN(conntor.dsn)
+
+	xgConn := &xuguConn{conn: nil}
+	xgConn.dsnConfig = dsnConfig
+
+	nd := net.Dialer{Timeout: 10 * time.Millisecond}
+	netConn, err := nd.DialContext(ctx, "tcp", fmt.Sprintf("%s:%s", xgConn.IP, xgConn.Port))
+	if err != nil {
+		return nil, err
+	}
+
+	// 启用 TCP 保活
+	if tc, ok := netConn.(*net.TCPConn); ok {
+		if err := tc.SetKeepAlive(true); err != nil {
+			//c.cfg.Logger.Print(err) // 如果设置保活失败,记录错误但不终止
+			return nil, err
+		}
+	}
+
+	xgConn.conn = netConn
+	xgConn.mu.Lock()
+	xgConn.readBuff = newBuffer(xgConn.conn)
+	err = xgSockOpenConn(ctx, xgConn)
+	if err != nil {
+		return nil, err
+	}
+	xgConn.mu.Unlock()
+	return xgConn, nil
+}

+ 32 - 0
pkg/xugu/xugu_define.go

@@ -0,0 +1,32 @@
+package xugu
+
+var GlobalIsBig bool
+
+// 协议消息类型
+type msgType byte
+
+const (
+	selectResult msgType = iota + 0x01
+	insertResult
+	updateResult
+	deleteResult
+	procRet
+	outParamRet
+	errInfo
+	warnInfo
+	message
+	formArgDescri
+)
+
+// SQL类型常量
+const (
+	SQL_UNKNOWN = iota
+	SQL_SELECT
+	SQL_INSERT
+	SQL_UPDATE
+	SQL_DELETE
+	SQL_CREATE
+	SQL_ALTER
+	SQL_PROCEDURE
+	SQL_OTHER
+)

+ 51 - 0
pkg/xugu/xugu_driver.go

@@ -0,0 +1,51 @@
+package xugu
+
+import (
+	"context"
+	"database/sql"
+	"database/sql/driver"
+	"fmt"
+	"time"
+)
+
+// XuguDriver is exported to make the driver directly accessible
+type XuguDriver struct{}
+
+/* Register Driver */
+func init() {
+
+	/* Register makes a database driver available by the provided name.
+	 * If Register is called twice with the same name or if driver is nil,
+	 * it panics.
+	 */
+	sql.Register("xugu", &XuguDriver{})
+	timezone, _ := time.LoadLocation("Asia/Shanghai")
+	time.Local = timezone
+}
+
+// Open opens a database specified by its database driver name and a
+// driver-specific data source name, usually consisting of at least a
+// database name and connection information.
+//
+// Most users will open a database via a driver-specific connection
+// helper function that returns a *DB. No database drivers are included
+// in the Go standard library. See https://golang.org/s/sqldrivers for
+// a list of third-party drivers.
+//
+// Open may just validate its arguments without creating a connection
+// to the database. To verify that the data source name is valid, call
+// Ping.
+// The returned DB is safe for concurrent use by multiple goroutines
+// and maintains its own pool of idle connections. Thus, the Open
+// function should be called just once. It is rarely necessary to
+// close a DB.
+func (db XuguDriver) Open(dsn string) (driver.Conn, error) {
+
+	conn := &connector{dsn: dsn}
+	return conn.Connect(context.Background())
+}
+
+func (db XuguDriver) OpenConnector(dsn string) (driver.Connector, error) {
+	fmt.Println("GlobalIsBig走了吗 ")
+	return &connector{dsn: dsn}, nil
+}

+ 207 - 0
pkg/xugu/xugu_fields.go

@@ -0,0 +1,207 @@
+package xugu
+
+import (
+	"database/sql"
+	"reflect"
+	"time"
+)
+
+type FieldDescri struct {
+	FieldNameLen    int
+	FieldName       string
+	FieldType       fieldType
+	FieldPreciScale fieldPreciScaleInfo
+	FieldFlag       uint32
+}
+
+type fieldPreciScaleInfo struct {
+	scale    uint16
+	accuracy uint16
+}
+
+type fieldType uint16
+
+const (
+	fieldType_EMPTY fieldType = iota + 0x00
+	fieldType_NULL
+	fieldType_BOOL
+	fieldType_I1
+	fieldType_I2
+	fieldType_I4
+	fieldType_I8
+	fieldType_NUM
+	fieldType_R4
+	fieldType_R8
+
+	fieldType_DATE
+	fieldType_TIME
+	fieldType_TIME_TZ
+	fieldType_DATETIME
+	fieldType_DATETIME_TZ
+
+	fieldType_INTERVAL_Y
+	fieldType_INTERVAL_Y2M
+	fieldType_INTERVAL_M
+
+	fieldType_INTERVAL_D
+	fieldType_INTERVAL_D2H
+	fieldType_INTERVAL_H
+	fieldType_INTERVAL_D2M
+	fieldType_INTERVAL_H2M
+	fieldType_INTERVAL_MI
+	fieldType_INTERVAL_D2S
+	fieldType_INTERVAL_H2S
+	fieldType_INTERVAL_M2S
+	fieldType_INTERVAL_S
+
+	fieldType_ROWVER
+	fieldType_GUID
+	fieldType_CHAR
+	fieldType_NCHAR
+	fieldType_CLOB
+
+	fieldType_BINARY
+	fieldType_BLOB
+
+	fieldType_GEOM
+	fieldType_POINT
+	fieldType_BOX
+	fieldType_POLYLINE
+	fieldType_POLYGON
+
+	fieldType_BLOB_I
+	fieldType_BLOB_S
+	fieldType_BLOB_M
+	fieldType_BLOB_OM
+	fieldType_STREAM
+	fieldType_ROWID
+	fieldType_SIBLING
+	fieldType_MAX_SYS fieldType = 47
+
+	fieldType_BLADE_BEGIN fieldType = 101
+	fieldType_BLADE_END   fieldType = 1000
+
+	fieldType_OBJECT fieldType = 1001 // object type
+	fieldType_REFROW
+	fieldType_RECORD  // record type
+	fieldType_VARRAY  // array type
+	fieldType_TABLE   // table type
+	fieldType_ITABLE  // Idxby table
+	fieldType_CURSOR  // involved ref-record type (cannot change)
+	fieldType_REFCUR  // REF_CURSOR type
+	fieldType_ROWTYPE // ref row type
+	fieldType_COLTYPE // ref column type
+	fieldType_CUR_REC
+	fieldType_PARAM
+)
+
+/* {{ */
+func (self *FieldDescri) typeDatabaseName() string {
+	switch self.FieldType {
+	case fieldType_BOOL:
+		return "BOOLEAN"
+	case fieldType_CHAR, fieldType_NCHAR:
+		return "CHAR"
+	case fieldType_I1:
+		return "TINYINT"
+	case fieldType_I2:
+		return "SHORT"
+	case fieldType_I4:
+		return "INTEGER"
+	case fieldType_I8:
+		return "BIGINT"
+	case fieldType_R4:
+		return "FLOAT"
+	case fieldType_R8:
+		return "DOUBLE"
+	case fieldType_NUM:
+		return "NUMERIC"
+	case fieldType_DATE:
+		return "DATE"
+	case fieldType_TIME:
+		return "TIME"
+	case fieldType_TIME_TZ:
+		return "TIMEZONE"
+	case fieldType_DATETIME:
+		return "DATETIME"
+	case fieldType_DATETIME_TZ:
+		return "DATETIME TIMEZONE"
+	case fieldType_BINARY:
+		return "BINARY"
+	case fieldType_INTERVAL_Y:
+		return "INTERVAL YEAR"
+	case fieldType_INTERVAL_Y2M:
+		return "INTERVAL YEAR TO MONTH"
+	case fieldType_INTERVAL_D2S:
+		return "INTERVAL DAY TO SECOND"
+	case fieldType_CLOB:
+		return "CLOB"
+	case fieldType_BLOB:
+		return "BLOB"
+	default:
+		return ""
+	}
+}
+
+/* {{ */
+func (self *FieldDescri) scanType() reflect.Type {
+
+	switch self.FieldType {
+
+	case fieldType_BOOL:
+		return scanTypeBool
+	case fieldType_I1:
+		return scanTypeInt8
+	case fieldType_I2:
+		return scanTypeInt16
+	case fieldType_I4:
+		return scanTypeInt32
+	case fieldType_I8:
+		return scanTypeInt64
+	case fieldType_R4:
+		return scanTypeFloat32
+	case fieldType_R8:
+		return scanTypeFloat64
+	case fieldType_DATE,
+		fieldType_TIME,
+		fieldType_DATETIME:
+		return scanTypeNullTime
+	case fieldType_TIME_TZ,
+		fieldType_DATETIME_TZ,
+		fieldType_CHAR,
+		fieldType_NCHAR,
+		fieldType_BINARY,
+		//fieldTypeInterval,
+		fieldType_NUM,
+		fieldType_INTERVAL_Y2M,
+		fieldType_INTERVAL_D2S,
+		//fieldTypeLob,
+		fieldType_CLOB,
+		fieldType_BLOB:
+		return scanTypeRawBytes
+	default:
+		return scanTypeUnknown
+
+	}
+}
+
+var (
+	scanTypeFloat32   = reflect.TypeOf(float32(0))
+	scanTypeFloat64   = reflect.TypeOf(float64(0))
+	scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
+	scanTypeNullInt   = reflect.TypeOf(sql.NullInt64{})
+	scanTypeNullTime  = reflect.TypeOf(time.Time{})
+	scanTypeInt8      = reflect.TypeOf(int8(0))
+	scanTypeInt16     = reflect.TypeOf(int16(0))
+	scanTypeInt32     = reflect.TypeOf(int32(0))
+	scanTypeInt64     = reflect.TypeOf(int64(0))
+	scanTypeUnknown   = reflect.TypeOf(new(interface{}))
+	scanTypeRawBytes  = reflect.TypeOf(sql.RawBytes{})
+	scanTypeUint8     = reflect.TypeOf(uint8(0))
+	scanTypeUint16    = reflect.TypeOf(uint16(0))
+	scanTypeUint32    = reflect.TypeOf(uint32(0))
+	scanTypeUint64    = reflect.TypeOf(uint64(0))
+	scanTypeBool      = reflect.TypeOf(bool(false))
+)
+
+

+ 85 - 0
pkg/xugu/xugu_model.go

@@ -0,0 +1,85 @@
+package xugu
+
+type SelectResult struct {
+	Field_Num uint32
+	Fields    []FieldDescri
+	Values    [][]FieldValue //[字段][字段所有值]
+	rowIdx    int
+	fad       *FormArgDescri
+	next      *SelectResult
+}
+type FormArgDescri struct {
+	ArgNum uint32
+	Args   []ArgDescri
+}
+
+type FieldValue struct {
+	Col_len  uint32
+	Col_Data []byte
+}
+
+type InsertResult struct {
+	EffectNum uint32
+	RowidLen  uint32
+	RowidData []byte
+}
+
+type UpdateResult struct {
+	UpdateNum uint32
+}
+
+type DeleteResult struct {
+	DeleteNum uint32
+}
+
+type ProcRet struct {
+	RetDType   uint32
+	RetDataLen uint32
+	RetData    []byte
+}
+
+type OutParamRet struct {
+	OutParamNo    uint32
+	OutParamDType uint32
+	OutParamLen   uint32
+	OutParamData  []byte
+}
+
+type ErrInfo struct {
+	ErrStrLen uint32
+	ErrStr    []byte
+}
+
+type WarnInfo struct {
+	WarnStrLen uint32
+	WarnStr    []byte
+}
+
+type Message struct {
+	MsgStrLen uint32
+	MsgStr    []byte
+}
+
+type ArgDescri struct {
+	ArgNameLen    uint32
+	ArgName       []byte
+	ArgNo         uint32
+	ArgDType      uint32
+	ArgPreciScale uint32
+}
+
+type allResult struct {
+	rt        msgType
+	s         *SelectResult
+	i         *InsertResult
+	u         *UpdateResult
+	d         *DeleteResult
+	p         *ProcRet
+	o         *OutParamRet
+	e         *ErrInfo
+	w         *WarnInfo
+	m         *Message
+	f         *FormArgDescri
+	next      *allResult
+	effectNum uint32 //sql总影响行数,包括i,u,d
+}

+ 520 - 0
pkg/xugu/xugu_parse.go

@@ -0,0 +1,520 @@
+package xugu
+
+import (
+	"database/sql/driver"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type xuguValue struct {
+	// 布尔值,如果值为 true,表示当前字段的数据类型是大对象数据类型
+	islob bool
+	//字段名
+	paramName []byte
+	//paramNameLength [2]byte
+	// 字段的实际值
+	value []byte
+	// 值的长度
+	valueLength int
+	// 字段的类型
+	types fieldType
+}
+
+// 判断参数个数
+func assertParamCount(query string) int {
+
+	paramCount := strings.Count(query, "?")
+
+	return paramCount
+}
+func assertParamType(dV driver.Value, values *[]xuguValue) error {
+	var dest xuguValue
+	switch srcv := dV.(type) {
+
+	case int64:
+		buf := make([]byte, 8)
+		binary.BigEndian.PutUint64(buf, uint64(srcv))
+		dest.value = buf
+		dest.valueLength = 8
+		dest.islob = false
+		dest.types = fieldType_I8
+
+	case float64:
+		S := strconv.FormatFloat(srcv, 'f', 15, 64)
+		dest.value = []byte(S)
+		dest.valueLength = len(S)
+		dest.islob = false
+		dest.types = fieldType_CHAR
+
+	case bool:
+		//S := strconv.FormatBool(srcv)
+		var tmp []byte
+		if srcv {
+			tmp = []byte{1}
+		} else {
+			tmp = []byte{0}
+		}
+
+		dest.value = []byte(tmp)
+		dest.valueLength = 1
+		dest.islob = false
+		dest.types = fieldType_BOOL
+
+	case string:
+		dest.value = []byte(srcv)
+		dest.valueLength = len(srcv)
+		dest.islob = false
+		dest.types = fieldType_CHAR
+		if dest.valueLength == 0 {
+			dest.valueLength = 1
+			dest.value = []byte{0}
+		}
+
+	case time.Time:
+		tm := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d",
+			srcv.Year(), int(srcv.Month()), srcv.Day(),
+			srcv.Hour(), srcv.Minute(), srcv.Second())
+		dest.value = []byte(tm)
+		//	dest.valueLength = strings.Count(tm, "") - 1
+		dest.valueLength = len(tm)
+		dest.islob = false
+		//dest.types = fieldType_TIME
+		dest.types = fieldType_CHAR
+	case []byte:
+		dest.value = srcv
+		dest.valueLength = len(srcv)
+		dest.islob = true
+		dest.types = fieldType_BLOB
+		if dest.valueLength == 0 {
+			dest.valueLength = 1
+			dest.value = []byte{0}
+		}
+
+	case nil:
+		dest.value = nil
+		dest.valueLength = 0
+		dest.islob = false
+		dest.types = fieldType_NULL
+
+	default:
+		return errors.New("unknown data type")
+	}
+
+	*values = append(*values, dest)
+	return nil
+}
+
+func parseMsg(readBuf *buffer, pConn *xuguConn) (*allResult, error) {
+	var err error
+	i := 0
+	//aR := allResult{}
+	//head node
+	aRPoint := &allResult{}
+	//move node
+	aR := aRPoint
+	for {
+		char := readBuf.peekChar()
+		if i != 0 {
+			re := &allResult{}
+			//re.effectNum = aR.effectNum
+			aR.next = re
+			aR = re
+		}
+		i++
+		switch char {
+
+		case 'K':
+			readBuf.reset()
+			return aRPoint, nil
+
+		case '$':
+			readBuf.idx++
+			if aR.f, err = parseFormArgDescri(readBuf); err != nil {
+				return nil, err
+			}
+			aR.rt = formArgDescri
+			//return &aR, err
+		case 'A':
+			readBuf.idx++
+			if aR.s, err = parseSelectResult(readBuf); err != nil {
+				return nil, err
+			}
+			aR.rt = selectResult
+		//	return &aR, err
+
+		case 'I':
+			readBuf.idx++
+			if aR.i, err = parseInsertResult(readBuf); err != nil {
+				return nil, err
+			}
+			aRPoint.effectNum++
+			aRPoint.i.EffectNum = aRPoint.effectNum
+			aR.rt = insertResult
+			//return &aR, err
+
+		case 'U':
+			readBuf.idx++
+			if aR.u, err = parseUpdateResult(readBuf); err != nil {
+				return nil, err
+			}
+			aR.rt = updateResult
+			//return &aR, err
+
+		case 'D':
+			readBuf.idx++
+			if aR.d, err = parseDeleteResult(readBuf); err != nil {
+				return nil, err
+			}
+			aR.rt = deleteResult
+		//	return &aR, err
+
+		case 'E':
+			readBuf.idx++
+
+			if aR.e, err = parseErrInfo(readBuf); err != nil {
+				return nil, err
+			}
+			pConn.errStr = aR.e.ErrStr
+			aR.rt = errInfo
+			//return &aR, err
+
+		case 'W':
+			readBuf.idx++
+			if aR.w, err = parseWarnInfo(readBuf); err != nil {
+				return nil, err
+			}
+			aR.rt = warnInfo
+			//return &aR, err
+
+		case 'M':
+			readBuf.idx++
+			if aR.m, err = parseMessage(readBuf); err != nil {
+				return nil, err
+			}
+			aR.rt = message
+			//return &aR, err
+
+		default:
+			return nil, errors.New("parseMsg: unknown message type")
+		}
+
+	}
+}
+func parseSelectResult(readBuf *buffer) (*SelectResult, error) {
+	data := &SelectResult{}
+
+	var char byte
+
+	//Field_Num
+	fn, err := readBuf.readNext(4, true)
+	if err != nil {
+		return nil, err
+	}
+
+	Field_Num := binary.LittleEndian.Uint32(fn)
+	data.Field_Num = Field_Num
+	data.rowIdx = 0
+
+	//获取字段信息
+	for i := 0; i < int(Field_Num); i++ {
+
+		field := FieldDescri{}
+
+		//Field_Name_Len
+		Field_Name_Len, err := readBuf.readNext(4, true)
+		if err != nil {
+			return nil, err
+		}
+		field.FieldNameLen = int(binary.LittleEndian.Uint32(Field_Name_Len))
+
+		//Field_Name:
+		Field_Name, err := readBuf.readNext(field.FieldNameLen, false)
+		if err != nil {
+			return nil, err
+		}
+		parts := strings.Split(string(Field_Name), ".")
+		field.FieldName = parts[len(parts)-1]
+
+		//Field_DType:
+		Field_DType, err := readBuf.readNext(4, true)
+		if err != nil {
+			return nil, err
+		}
+		field.FieldType = fieldType(binary.LittleEndian.Uint32(Field_DType))
+
+		//Field_Preci_Scale:
+		Field_Preci_Scale, err := readBuf.readNext(4, true)
+		if err != nil {
+			return nil, err
+		}
+
+		fieldPreciScale := binary.LittleEndian.Uint32(Field_Preci_Scale)
+		if int32(fieldPreciScale) <= 0 {
+			field.FieldPreciScale = fieldPreciScaleInfo{
+				scale:    0,
+				accuracy: 0,
+			}
+		} else {
+			field.FieldPreciScale = fieldPreciScaleInfo{
+				scale:    uint16(fieldPreciScale >> 16),
+				accuracy: uint16(fieldPreciScale & 0xFFFF),
+			}
+		}
+
+		//Field_Flag:
+		Field_Flag, err := readBuf.readNext(4, true)
+		if err != nil {
+			return nil, err
+		}
+		field.FieldFlag = binary.LittleEndian.Uint32(Field_Flag)
+		data.Fields = append(data.Fields, field)
+	}
+
+	data.Values = make([][]FieldValue, data.Field_Num)
+
+	//获取字段的行值,并判断类型
+	// 使用 Peek 方法检查下一个字节是否为'R'或'K'
+	for {
+		char = readBuf.peekChar()
+		//readBuf.idx++
+		if char == 'K' {
+			return data, nil
+		} else if char == 'R' { //接收字段信息后不是k 那一定是 R
+
+			colIdx := 0
+			//typeIdx := 0
+			readBuf.idx++
+			for i := 0; i < int(Field_Num); i++ {
+				col := FieldValue{}
+				//获取数据的大小
+				Col_len, err := readBuf.readNext(4, true)
+				if err != nil {
+					return nil, err
+				}
+				col.Col_len = binary.LittleEndian.Uint32(Col_len)
+
+				//获取数据的值
+				col.Col_Data, err = readBuf.readNext(int(col.Col_len), false)
+				if err != nil {
+					return nil, err
+				}
+
+				data.Values[colIdx] = append(data.Values[colIdx], col)
+				colIdx++
+
+			} //for end
+
+		} else if char == '$' {
+			fad, err := parseFormArgDescri(readBuf)
+			if err != nil {
+				return nil, err
+			}
+
+			char := readBuf.peekChar()
+			//既不是R 也不是K 代表该行还有其他字段内容没有读取完成
+			if char == 'K' {
+				data.fad = fad
+				return data, nil
+				//break
+			}
+
+			return nil, errors.New("select to $ parse failed")
+		} else {
+			return data, nil
+		}
+	}
+
+}
+
+func parseInsertResult(readBuf *buffer) (*InsertResult, error) {
+
+	//Rowid_Len
+	Rowid_L, err := readBuf.readNext(4, true)
+	if err != nil {
+		return nil, err
+	}
+	Rowid_Len := binary.LittleEndian.Uint32(Rowid_L)
+	//Rowid_Data
+	encoded, err := readBuf.readNext(int(Rowid_Len), false)
+	if err != nil {
+		return nil, err
+	}
+
+	//检测是否结束
+	// char := readBuf.peekChar()
+
+	// if char == 'K' {
+	// 	return &InsertResult{
+	// 		RowidLen:  Rowid_Len,
+	// 		RowidData: encoded,
+	// 	}, nil
+	// }
+
+	return &InsertResult{
+		RowidLen:  Rowid_Len,
+		RowidData: encoded,
+	}, nil
+}
+
+func parseUpdateResult(readBuf *buffer) (*UpdateResult, error) {
+	updatas, err := readBuf.readNext(4, true)
+	if err != nil {
+		return nil, err
+	}
+	updateNum := binary.LittleEndian.Uint32(updatas)
+
+	return &UpdateResult{UpdateNum: updateNum}, nil
+}
+
+func parseDeleteResult(readBuf *buffer) (*DeleteResult, error) {
+	deletes, err := readBuf.readNext(4, true)
+	if err != nil {
+		return nil, err
+	}
+	deleteNum := binary.LittleEndian.Uint32(deletes)
+
+	return &DeleteResult{DeleteNum: deleteNum}, nil
+}
+
+func parseProcRet(readBuf *buffer) (*ProcRet, error) {
+	retDypes, err := readBuf.readNext(4, true)
+	if err != nil {
+		return nil, err
+	}
+	retDType := binary.LittleEndian.Uint32(retDypes)
+	retDataLens, err := readBuf.readNext(4, true)
+	if err != nil {
+		return nil, err
+	}
+	retDataLen := binary.LittleEndian.Uint32(retDataLens)
+	retData, err := readBuf.readNext(int(retDataLen), false)
+	if err != nil {
+		return nil, err
+	}
+
+	return &ProcRet{RetDType: retDType, RetDataLen: retDataLen, RetData: retData}, nil
+}
+
+func parseOutParamRet(readBuf *buffer) (*OutParamRet, error) {
+	outParamNos, err := readBuf.readNext(4, true)
+	if err != nil {
+		return nil, err
+	}
+	outParamNo := binary.LittleEndian.Uint32(outParamNos)
+
+	outParamDTypes, err := readBuf.readNext(4, true)
+	if err != nil {
+		return nil, err
+	}
+	outParamDType := binary.LittleEndian.Uint32(outParamDTypes)
+
+	outParamLens, err := readBuf.readNext(4, true)
+	if err != nil {
+		return nil, err
+	}
+	outParamLen := binary.LittleEndian.Uint32(outParamLens)
+
+	outParamData, err := readBuf.readNext(int(outParamLen), false)
+	if err != nil {
+		return nil, err
+	}
+
+	return &OutParamRet{
+		OutParamNo:    outParamNo,
+		OutParamDType: outParamDType,
+		OutParamLen:   outParamLen,
+		OutParamData:  outParamData,
+	}, nil
+}
+
+func parseErrInfo(readBuf *buffer) (*ErrInfo, error) {
+	errStrLens, err := readBuf.readNext(4, true)
+	if err != nil {
+		return nil, err
+	}
+	errStrLen := binary.LittleEndian.Uint32(errStrLens)
+
+	errStr, err := readBuf.readNext(int(errStrLen), false)
+	if err != nil {
+		return nil, err
+	}
+	return &ErrInfo{ErrStrLen: errStrLen, ErrStr: errStr}, nil
+
+}
+
+func parseWarnInfo(readBuf *buffer) (*WarnInfo, error) {
+	warnStrLens, err := readBuf.readNext(4, true)
+	if err != nil {
+		return nil, err
+	}
+	warnStrLen := binary.LittleEndian.Uint32(warnStrLens)
+
+	warnStr, err := readBuf.readNext(int(warnStrLen), false)
+	if err != nil {
+		return nil, err
+	}
+	return &WarnInfo{WarnStrLen: warnStrLen, WarnStr: warnStr}, nil
+}
+
+func parseMessage(readBuf *buffer) (*Message, error) {
+	msgStrLens, err := readBuf.readNext(4, true)
+	if err != nil {
+		return nil, err
+	}
+	msgStrLen := binary.LittleEndian.Uint32(msgStrLens)
+
+	msgStr, err := readBuf.readNext(int(msgStrLen), false)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Message{MsgStrLen: msgStrLen, MsgStr: msgStr}, nil
+}
+
+func parseFormArgDescri(readBuf *buffer) (*FormArgDescri, error) {
+	//	FormArgDescri:   '$' Arg_Num { Arg_Name_Len Arg_Name Arg_No Arg_DType Arg_Preci_Scale }+
+	Arg_Nums, err := readBuf.readNext(4, true)
+	if err != nil {
+		return nil, err
+	}
+	Arg_Num := binary.LittleEndian.Uint32(Arg_Nums)
+	formArgDescri := &FormArgDescri{ArgNum: Arg_Num}
+	for i := 0; i < int(Arg_Num); i++ {
+		arg := ArgDescri{}
+		//Arg_Name_Len
+		ArgNameLen, err := readBuf.readNext(4, true)
+		if err != nil {
+			return nil, err
+		}
+		arg.ArgNameLen = binary.LittleEndian.Uint32(ArgNameLen)
+		//Arg_Name
+		arg.ArgName, err = readBuf.readNext(int(arg.ArgNameLen), false)
+		if err != nil {
+			return nil, err
+		}
+		//Arg_No
+		ArgNo, err := readBuf.readNext(4, true)
+		if err != nil {
+			return nil, err
+		}
+		arg.ArgNo = binary.LittleEndian.Uint32(ArgNo)
+		//Argg_DType
+		ArgDType, err := readBuf.readNext(4, true)
+		if err != nil {
+			return nil, err
+		}
+		arg.ArgDType = binary.LittleEndian.Uint32(ArgDType)
+		//Arg_Preci_Scale
+		ArgPreciScale, err := readBuf.readNext(4, true)
+		if err != nil {
+			return nil, err
+		}
+		arg.ArgPreciScale = binary.LittleEndian.Uint32(ArgPreciScale)
+		formArgDescri.Args = append(formArgDescri.Args, arg)
+	}
+	return formArgDescri, nil
+}

+ 156 - 0
pkg/xugu/xugu_parse_time.go

@@ -0,0 +1,156 @@
+package xugu
+
+type TIMESTAMP struct {
+	year     int
+	month    int
+	day      int
+	hour     int
+	minute   int
+	second   int
+	fraction int // 毫秒
+}
+
+// 是否是闰年
+func IsLeapYear(year int) bool {
+	return (year%4 == 0 && year%100 != 0) || (year%400 == 0)
+}
+
+// 月份到天数的映射
+var mtod = [2][13]int{
+	{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, // 非闰年
+	{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}, // 闰年
+}
+
+func dt2dtm(t int64) TIMESTAMP {
+	var (
+		y, m, d, s int
+		mm, nn     int
+		wday       int
+		ms         int
+		rn_num     int
+	)
+
+	if t >= 0 { // 1970年以后
+		ms = int(t % 1000)
+		t /= 1000
+		s = int(t % 86400)
+		d = int(t / 86400)
+		wday = (d + 4) % 7
+		mm = d / 146097
+		nn = d % 146097
+		y = 1970 + 400*mm
+		mm = nn / 36524
+		nn = nn % 36524
+		y += 100 * mm
+		mm = nn / 1461
+		nn = nn % 1461
+		y += 4 * mm
+		if nn > 1096 {
+			y += 3
+		}
+		if nn > 730 && nn <= 1096 {
+			y += 2
+		}
+		if nn > 365 && nn <= 730 {
+			y++
+		}
+		if nn == 0 {
+			y--
+		}
+		rn_num = (y-1)/4 - (y-1)/100 + (y-1)/400
+		rn_num -= 477
+		d = d - 365*(y-1970) - rn_num
+	} else { // 1970年以前
+		ms = int(t % 1000)
+		t /= 1000
+		if ms != 0 {
+			ms += 1000
+			t--
+		}
+		s = int(t % 86400)
+		d = int(t / 86400)
+		if s != 0 {
+			s += 86400
+			d--
+		}
+		wday = (d + 4) % 7
+		if wday < 0 {
+			wday += 7
+		}
+		mm = d / 146097
+		nn = d % 146097
+		y = 1969 + 400*mm
+		mm = nn / 36524
+		nn = nn % 36524
+		y += 100 * mm
+		mm = nn / 1461
+		nn = nn % 1461
+		y += 4 * mm
+		if nn < -1096 {
+			y -= 3
+		}
+		if nn < -731 && nn >= -1096 {
+			y -= 2
+		}
+		if nn < -365 && nn >= -731 {
+			y--
+		}
+		if nn == 0 {
+			y++
+		}
+		rn_num = y/4 - y/100 + y/400
+		rn_num -= 477
+		d = d - 365*(y+1-1970) - rn_num
+		if IsLeapYear(y) {
+			d += 366
+		} else {
+			d += 365
+		}
+	}
+
+	if d < 0 {
+		y--
+		if IsLeapYear(y) {
+			d += 366
+		} else {
+			d += 365
+		}
+	}
+
+	d++
+	if IsLeapYear(y) {
+		if d > 366 {
+			d -= 366
+			y++
+		}
+	} else if d > 365 {
+		d -= 365
+		y++
+	}
+
+	if IsLeapYear(y) {
+		for m = 0; m <= 11; m++ {
+			if d > mtod[1][m] && d <= mtod[1][m+1] {
+				d -= mtod[1][m]
+				break
+			}
+		}
+	} else {
+		for m = 0; m <= 11; m++ {
+			if d > mtod[0][m] && d <= mtod[0][m+1] {
+				d -= mtod[0][m]
+				break
+			}
+		}
+	}
+
+	return TIMESTAMP{
+		year:     y,
+		month:    m + 1,
+		day:      d,
+		hour:     s / 3600,
+		minute:   (s % 3600) / 60,
+		second:   s % 60,
+		fraction: ms,
+	}
+}

+ 48 - 0
pkg/xugu/xugu_result.go

@@ -0,0 +1,48 @@
+package xugu
+
+import (
+	"encoding/binary"
+	"fmt"
+)
+
+type xuguResult struct {
+	xgConn *xuguConn
+	// Returns the number of rows affected
+	// by update, delete and other related operations
+	affectedRows int64
+
+	// Returns the GUID number of
+	// the insert operation (not supported)
+	insertId int64
+}
+
+// LastInsertId returns the integer generated by the database
+// in response to a command. Typically this will be from an
+// "auto increment" column when inserting a new row. Not all
+// databases support this feature, and the syntax of such
+// statements varies.
+func (result *xuguResult) LastInsertId() (int64, error) {
+	sockSendPutStatement(result.xgConn, []byte("select last_insert_id();"), nil, 0)
+	sockSendExecute(result.xgConn)
+	//接收结果
+	//recv msg
+	aR, err := xuguSockRecvMsg(result.xgConn)
+	if err != nil {
+		return 0, err
+	}
+
+	switch aR.rt {
+	case selectResult:
+
+		return int64(binary.BigEndian.Uint64(aR.s.Values[0][0].Col_Data)), nil
+	}
+
+	return 0, fmt.Errorf("last insert id error")
+}
+
+// RowsAffected returns the number of rows affected by an
+// update, insert, or delete. Not every database or database
+// driver may support this.
+func (result *xuguResult) RowsAffected() (int64, error) {
+	return result.affectedRows, nil
+}

+ 196 - 0
pkg/xugu/xugu_rows.go

@@ -0,0 +1,196 @@
+package xugu
+
+import (
+	"database/sql/driver"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"math"
+	"reflect"
+	"time"
+)
+
+type xuguRows struct {
+	rows_conn *xuguConn
+	aR        *allResult
+	results   *SelectResult
+	colIdx    int
+	prepared  bool
+}
+
+func (row *xuguRows) Next(dest []driver.Value) error {
+
+	if row.results.rowIdx >= len(row.results.Values[0]) {
+		//return errors.New("The result set has been released")
+		return io.EOF
+	}
+
+	for j := 0; j < int(row.results.Field_Num); j++ {
+
+		coluType := row.results.Fields[j].FieldType
+		if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
+			dest[j] = nil
+		} else {
+			switch coluType {
+
+			case fieldType_BINARY,
+				fieldType_CLOB,
+				fieldType_BLOB:
+				dest[j] = row.results.Values[j][row.results.rowIdx].Col_Data
+
+			case fieldType_INTERVAL_Y, fieldType_INTERVAL_M, fieldType_INTERVAL_D,
+				fieldType_INTERVAL_H, fieldType_INTERVAL_S,
+				fieldType_INTERVAL_MI:
+				dest[j] = binary.BigEndian.Uint32(row.results.Values[j][row.results.rowIdx].Col_Data)
+
+			case fieldType_TIME,
+				fieldType_TIME_TZ:
+				timeTmp := int32(binary.BigEndian.Uint32(row.results.Values[j][row.results.rowIdx].Col_Data))
+				//tv, _ := time.Parse("2006-01-02 15:04:05", string(reverseBytes(row.results.Values[j][row.results.rowIdx].Col_Data)))
+				tv := time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC).Add((time.Millisecond * time.Duration(timeTmp)))
+				utcTime := tv.UTC()
+				dest[j] = utcTime
+			case fieldType_DATE:
+
+				timeTmp := int32(binary.BigEndian.Uint32(row.results.Values[j][row.results.rowIdx].Col_Data))
+				//tv, _ := time.Parse("2006-01-02 15:04:05", string(reverseBytes(row.results.Values[j][row.results.rowIdx].Col_Data)))
+				tv := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC).Add((time.Hour * 24 * time.Duration(timeTmp)))
+				utcTime := tv.UTC()
+				dest[j] = utcTime
+				// rintln(string(row.results.Values[j][row.results.rowIdx].Col_Data))
+				// tv, _ := time.Parse("15:04:05", string(row.results.Values[j][row.results.rowIdx].Col_Data))
+				// dest[j] = tv
+
+			case fieldType_DATETIME,
+				fieldType_DATETIME_TZ:
+
+				if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
+					dest[j] = nil
+				} else {
+					//timeTmp := binary.LittleEndian.Uint64(reverseBytes(row.results.Values[j][row.results.rowIdx].Col_Data)
+					timeTmp := binary.BigEndian.Uint64(row.results.Values[j][row.results.rowIdx].Col_Data)
+					//tv, _ := time.Parse("2006-01-02 15:04:05", string(reverseBytes(row.results.Values[j][row.results.rowIdx].Col_Data)))
+					tv := time.Unix(0, int64(timeTmp)*1000000)
+					utcTime := tv.UTC()
+					dest[j] = utcTime
+				}
+
+			case fieldType_R4:
+				if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
+					dest[j] = nil
+				} else {
+					dest[j] = math.Float32frombits(binary.BigEndian.Uint32(row.results.Values[j][row.results.rowIdx].Col_Data))
+				}
+
+			case fieldType_R8:
+				if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
+					dest[j] = nil
+				} else {
+					dest[j] = math.Float64frombits(binary.BigEndian.Uint64(row.results.Values[j][row.results.rowIdx].Col_Data))
+				}
+			case fieldType_NUM:
+				dest[j] = row.results.Values[j][row.results.rowIdx].Col_Data
+			case fieldType_I1:
+				if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
+					dest[j] = nil
+				} else {
+					dest[j] = int8(row.results.Values[j][row.results.rowIdx].Col_Data[0])
+				}
+			case fieldType_I2:
+				if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
+					dest[j] = nil
+				} else {
+					dest[j] = int16(binary.BigEndian.Uint16(row.results.Values[j][row.results.rowIdx].Col_Data))
+				}
+			case fieldType_I4:
+				if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
+					dest[j] = nil
+				} else {
+					dest[j] = int32(binary.BigEndian.Uint32(row.results.Values[j][row.results.rowIdx].Col_Data))
+				}
+			case fieldType_I8:
+				if len(row.results.Values[j][row.results.rowIdx].Col_Data) == 0 {
+					dest[j] = nil
+				} else {
+					dest[j] = int64(binary.BigEndian.Uint64(row.results.Values[j][row.results.rowIdx].Col_Data))
+					//dest[j] = row.results.Values[j][row.results.rowIdx].Col_Data
+				}
+			case fieldType_CHAR, fieldType_NCHAR:
+				if row.results.Values[j][row.results.rowIdx].Col_Data == nil {
+					dest[j] = string("")
+				} else if row.results.Values[j][row.results.rowIdx].Col_Data[0] == 0x00 {
+					dest[j] = string("")
+				} else {
+					dest[j] = string(row.results.Values[j][row.results.rowIdx].Col_Data)
+				}
+			default:
+
+				//填入一行的数据
+				//TODO这里长度改为一行长度
+				dest[j] = make([]byte, len(row.results.Values))
+				// Values[字段][0]
+				dest[j] = row.results.Values[j][row.results.rowIdx].Col_Data
+
+			}
+		}
+	}
+	row.results.rowIdx++
+
+	return nil
+}
+
+// Columns返回列的名字集,它的个数是从slice的长度中推断出来的。
+// 如果不知道特定的列名,应该为该条目返回一个空的字符串
+func (row *xuguRows) Columns() []string {
+
+	var columns []string
+
+	for _, v := range row.results.Fields {
+		columns = append(columns, v.FieldName)
+	}
+
+	return columns
+}
+
+func (row *xuguRows) ColumnTypeScanType(index int) reflect.Type {
+	//rintln(">>>>>ColumnTypeScanType ")
+
+	return row.results.Fields[index].scanType()
+}
+
+// The driver is at the end of the current result set.
+// Test to see if there is another result set after the current one.
+// Only close Rows if there is no further result sets to read.
+func (row *xuguRows) HasNextResultSet() bool {
+	return row.aR.next != nil
+
+}
+
+// NextResultSet prepares the next result set for reading. It reports whether
+// there is further result sets, or false if there is no further result set
+// or if there is an error advancing to it. The Err method should be consulted
+// to distinguish between the two cases.
+//
+// After calling NextResultSet, the Next method should always be called before
+// scanning. If there are further result sets they may not have rows in the result
+// set.
+func (row *xuguRows) NextResultSet() error {
+
+	if row.aR.next == nil {
+		return fmt.Errorf("there are no multiple result sets available")
+	}
+	switch row.aR.next.rt {
+	case errInfo:
+		return fmt.Errorf("error: %s", string(row.aR.next.e.ErrStr))
+
+	}
+
+	row.results = row.aR.next.s
+	row.aR = row.aR.next
+
+	return nil
+}
+
+func (row *xuguRows) Close() error {
+	return nil
+}

+ 130 - 0
pkg/xugu/xugu_sock.go

@@ -0,0 +1,130 @@
+package xugu
+
+import (
+	"bytes"
+	"context"
+	"encoding/binary"
+	"errors"
+	"fmt"
+)
+
+func xgSockOpenConn(ctx context.Context, pConn *xuguConn) error {
+	//发送
+	//rintf("login   database = '%s' user = '%s'  password = '%s' version='201' ", pConn.Database, pConn.User, pConn.Password)
+	//	message := "login   database = 'SYSTEM' user = 'SYSDBA'  password = 'SYSDBA' version='201' "
+	dsnMessage := generateLoginString(pConn.dsnConfig)
+	_, err := pConn.conn.Write([]byte(dsnMessage))
+	if err != nil {
+		return errors.New("向数据库发起连接失败")
+	}
+
+	buffer := make([]byte, 1)
+	n, err := pConn.conn.Read(buffer)
+	if err != nil {
+		return errors.New(fmt.Sprintln("接收数据库连接失败: ", err.Error()))
+	}
+
+	if !bytes.Equal(buffer[:n], []byte("K")) {
+		return errors.New("数据库连接失败")
+	} else {
+		return nil
+	}
+
+}
+
+func sockSendPutStatement(pConn *xuguConn, sql []byte, values *[]xuguValue, paramCount int) error {
+	if pConn.sendBuff.Len() > 0 {
+		//将缓冲区重置为空
+		pConn.sendBuff.Reset()
+	}
+	// ?
+	pConn.sendBuff.Write([]byte("?"))
+	// Comand_Len
+	sqlLength := uint32(len(sql))
+	var networkBytes [4]byte
+	binary.BigEndian.PutUint32(networkBytes[:], sqlLength)
+	pConn.sendBuff.Write(networkBytes[:])
+	//  Comand_str
+	pConn.sendBuff.Write(sql)
+	//'0' end
+	binary.BigEndian.PutUint32(networkBytes[:], 0)
+	pConn.sendBuff.Write([]byte{0})
+	// Param_num
+
+	var Param_num [4]byte
+	binary.BigEndian.PutUint32(Param_num[:], uint32(paramCount))
+	pConn.sendBuff.Write(Param_num[:])
+	if values != nil {
+		//当缓冲区大于8190字节时,直接发送
+		// if pConn.sendBuff.Len() > 8190 {
+		// 	_, err := pConn.conn.Write(pConn.sendBuff.Bytes())
+		// 	if err != nil {
+		// 		rintln("sockSend Write failed: ", err)
+		// 		return err
+		// 	}
+		// }
+
+		//发送后续参数
+		//	Param_num   { Param_name_len Param_name Param_INOUT Param_DType Param_Data_Len Param_Data }
+		for _, value := range *values {
+			//Param_name_len
+			if value.paramName == nil {
+				var Param_name_len [2]byte
+				pConn.sendBuff.Write(Param_name_len[:])
+				//Param_name
+				// var Param_name []byte
+				// pConn.sendBuff.Write(Param_name)
+
+			} else {
+				var Param_name_len [2]byte
+
+				binary.BigEndian.PutUint16(Param_name_len[:], uint16(len(value.paramName)))
+				pConn.sendBuff.Write(Param_name_len[:])
+
+				//Param_name
+				pConn.sendBuff.Write(value.paramName[:])
+
+			}
+
+			//Param_INOUT
+			Param_INOUT := [2]byte{0x1}
+			pConn.sendBuff.Write(reverseBytes(Param_INOUT[:]))
+
+			//Param_DType
+			var Param_DType [2]byte
+			binary.BigEndian.PutUint16(Param_DType[:], uint16(value.types))
+			pConn.sendBuff.Write(Param_DType[:])
+			//Param_Data_Len 根据DType 修改长度
+			Param_Data_Len := make([]byte, 4)
+			binary.BigEndian.PutUint32(Param_Data_Len[:], uint32(value.valueLength))
+			pConn.sendBuff.Write(Param_Data_Len[:])
+			//Param_Data 根据DType 修改长度
+			//Param_Data := make([]byte, value.valueLength)
+			pConn.sendBuff.Write([]byte(value.value))
+		}
+
+	}
+
+	return nil
+}
+
+func sockSendExecute(pConn *xuguConn) error {
+	//	rintln("SockSendExecute msg: ", pConn.sendBuff.String())
+	_, err := pConn.conn.Write(pConn.sendBuff.Bytes())
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func xuguSockRecvMsg(pConn *xuguConn) (*allResult, error) {
+	n, _ := pConn.conn.Read(pConn.readBuff.buf)
+	pConn.readBuff.length += n
+	rs, err := parseMsg(&pConn.readBuff, pConn)
+	if err != nil {
+
+		return nil, err
+	}
+	pConn.readBuff.reset()
+	return rs, nil
+}

+ 139 - 0
pkg/xugu/xugu_stmt.go

@@ -0,0 +1,139 @@
+package xugu
+
+import (
+	"database/sql/driver"
+	"encoding/binary"
+	"errors"
+)
+
+type xuguStmt struct {
+	stmt_conn *xuguConn
+
+	prepared bool
+
+	prename []byte
+
+	// 布尔值,用于标识游标是否启用
+	curopend bool
+
+	// 游标名称
+	curname []byte
+
+	// 执行的 SQL 语句中的参数数量
+	paramCount int
+	mysql      string
+
+	//需替换的参数字段信息
+	//parser *xuguParse
+}
+
+func (stmt *xuguStmt) Close() error {
+	//关闭 prepare
+	err := xuguUnPrepare(stmt.stmt_conn, stmt.stmt_conn.prepareName)
+	//释放资源
+
+	return err
+}
+
+func (stmt *xuguStmt) NumInput() int {
+
+	return assertParamCount(stmt.mysql)
+	//return 0
+}
+
+func (stmt *xuguStmt) Exec(args []driver.Value) (driver.Result, error) {
+
+	stmt.stmt_conn.mu.Lock()
+	defer stmt.stmt_conn.mu.Unlock()
+	//send msg
+	//如果有参数
+	if stmt.paramCount > 0 && len(args) > 0 {
+		values := []xuguValue{}
+		for _, param := range args {
+			assertParamType(param, &values)
+		}
+		sockSendPutStatement(stmt.stmt_conn, stmt.prename, &values, stmt.paramCount)
+		sockSendExecute(stmt.stmt_conn)
+		//没有参数
+	} else {
+		sockSendPutStatement(stmt.stmt_conn, []byte(stmt.mysql), nil, 0)
+		sockSendExecute(stmt.stmt_conn)
+	}
+
+	//recv msg
+	aR, err := xuguSockRecvMsg(stmt.stmt_conn)
+	if err != nil {
+		return nil, err
+	}
+	switch aR.rt {
+	case selectResult:
+
+		return nil, errors.New("exec is Query error")
+	case updateResult:
+		return &xuguResult{xgConn: stmt.stmt_conn, affectedRows: int64(aR.u.UpdateNum), insertId: int64(0)}, nil
+	case insertResult:
+		return &xuguResult{xgConn: stmt.stmt_conn, affectedRows: int64(aR.i.EffectNum), insertId: int64(binary.LittleEndian.Uint64(aR.i.RowidData))}, nil
+	case errInfo:
+
+		return nil, errors.New(string(aR.e.ErrStr))
+	case warnInfo:
+
+		return nil, errors.New(string(aR.w.WarnStr))
+	default:
+		return &xuguResult{
+			xgConn:       stmt.stmt_conn,
+			affectedRows: int64(0),
+			insertId:     int64(0),
+		}, nil
+	}
+
+}
+
+func (stmt *xuguStmt) Query(args []driver.Value) (driver.Rows, error) {
+
+	stmt.stmt_conn.mu.Lock()
+	defer stmt.stmt_conn.mu.Unlock()
+
+	//send msg
+	//如果有参数
+	if stmt.paramCount > 0 && len(args) > 0 {
+		values := []xuguValue{}
+		for _, param := range args {
+			assertParamType(param, &values)
+		}
+		sockSendPutStatement(stmt.stmt_conn, stmt.prename, &values, stmt.paramCount)
+		sockSendExecute(stmt.stmt_conn)
+		//没有参数
+	} else {
+		sockSendPutStatement(stmt.stmt_conn, []byte(stmt.mysql), nil, 0)
+		sockSendExecute(stmt.stmt_conn)
+	}
+
+	//recv msg
+	aR, err := xuguSockRecvMsg(stmt.stmt_conn)
+	if err != nil {
+		return nil, err
+	}
+
+	switch aR.rt {
+	case selectResult:
+
+		rows := &xuguRows{
+			rows_conn: stmt.stmt_conn,
+			results:   aR.s,
+			colIdx:    0,
+			prepared:  false,
+		}
+
+		return rows, nil
+	case errInfo:
+
+		return nil, errors.New(string(aR.e.ErrStr))
+	case warnInfo:
+
+		return nil, errors.New(string(aR.w.WarnStr))
+	default:
+	}
+
+	return nil, errors.New("xugu Query error")
+}

+ 36 - 0
pkg/xugu/xugu_tranx.go

@@ -0,0 +1,36 @@
+package xugu
+
+import "errors"
+
+type xuguTx struct {
+	tconn *xuguConn
+}
+
+func (tx *xuguTx) Commit() error {
+	tx.tconn.mu.Lock()
+	defer tx.tconn.mu.Unlock()
+
+	if tx.tconn == nil {
+		return errors.New("invalid connection")
+	}
+	_, err := tx.tconn.exec("commit;", nil)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (tx *xuguTx) Rollback() error {
+	tx.tconn.mu.Lock()
+	defer tx.tconn.mu.Unlock()
+
+	if tx.tconn == nil {
+		return errors.New("invalid connection")
+	}
+	_, err := tx.tconn.exec("rollback;", nil)
+	if err != nil {
+		return err
+	}
+
+	return err
+}

+ 189 - 0
pkg/xugu/xugu_utils.go

@@ -0,0 +1,189 @@
+package xugu
+
+import (
+	"encoding/binary"
+	"fmt"
+	"strings"
+)
+
+func parseDSN(dsn string) dsnConfig {
+	// Initialize a dsnConfig struct
+	var config dsnConfig
+
+	// Split the string by semicolons
+	pairs := strings.Split(dsn, ";")
+
+	// Iterate over the pairs and map them to the struct fields
+	for _, pair := range pairs {
+		// Split each pair by the equals sign
+		kv := strings.SplitN(pair, "=", 2)
+		if len(kv) != 2 {
+			continue
+		}
+		key, value := strings.TrimSpace(kv[0]), strings.Trim(strings.TrimSpace(kv[1]), "'")
+		keyL := strings.ToLower(key)
+
+		// Map the key to the appropriate struct field
+		switch keyL {
+		case "ip":
+			config.IP = value
+		case "port":
+			config.Port = value
+		case "db":
+			config.Database = value
+		case "user":
+			config.User = value
+		case "pwd":
+			config.Password = value
+		case "encryptor":
+			config.Encryptor = value
+		case "char_set":
+			config.CharSet = value
+		case "time_zone":
+			config.TimeZone = value
+		case "iso_level":
+			config.IsoLevel = value
+		case "lock_timeout":
+			config.LockTimeout = value
+		case "auto_commit":
+			config.AutoCommit = value
+		case "strict_commit":
+			config.StrictCommit = value
+		case "result":
+			config.Result = value
+		case "return_schema":
+			config.ReturnSchema = value
+		case "return_cursor_id":
+			config.ReturnCursorID = value
+		case "lob_ret":
+			config.LobRet = value
+		case "return_rowid":
+			config.ReturnRowid = value
+		case "version":
+			config.Version = value
+		}
+	}
+
+	return config
+}
+
+func generateLoginString(config dsnConfig) string {
+	baseString := "login   database = '%s' user = '%s'  password = '%s' "
+	additionalParams := ""
+
+	if config.Encryptor != "" {
+		additionalParams += fmt.Sprintf(" encryptor='%s'", config.Encryptor)
+	}
+	if config.CharSet != "" {
+		additionalParams += fmt.Sprintf(" char_set='%s'", config.CharSet)
+	}
+	if config.TimeZone != "" {
+		additionalParams += fmt.Sprintf(" time_zone='%s'", config.TimeZone)
+	}
+	if config.IsoLevel != "" {
+		additionalParams += fmt.Sprintf(" iso_level='%s'", config.IsoLevel)
+	}
+	if config.LockTimeout != "" {
+		additionalParams += fmt.Sprintf(" lock_timeout='%s'", config.LockTimeout)
+	}
+	if config.AutoCommit != "" {
+		additionalParams += fmt.Sprintf(" auto_commit='%s'", config.AutoCommit)
+	}
+	if config.StrictCommit != "" {
+		additionalParams += fmt.Sprintf(" strict_commit='%s'", config.StrictCommit)
+	}
+	if config.Result != "" {
+		additionalParams += fmt.Sprintf(" result='%s'", config.Result)
+	}
+	if config.ReturnSchema != "" {
+		additionalParams += fmt.Sprintf(" return_schema='%s'", config.ReturnSchema)
+	}
+	if config.ReturnCursorID != "" {
+		additionalParams += fmt.Sprintf(" return_cursor_id='%s'", config.ReturnCursorID)
+	}
+	if config.LobRet != "" {
+		additionalParams += fmt.Sprintf(" lob_ret='%s'", config.LobRet)
+	}
+	if config.ReturnRowid != "" {
+		additionalParams += fmt.Sprintf(" return_rowid='%s'", config.ReturnRowid)
+	}
+	if config.Version != "" {
+		additionalParams += fmt.Sprintf(" version='%s'", config.Version)
+	} else {
+		additionalParams += " version='201'"
+	}
+
+	finalString := fmt.Sprintf(baseString, config.Database, config.User, config.Password)
+	if additionalParams != "" {
+		finalString += additionalParams
+	}
+	//finalString += " version='201'"
+	return finalString
+}
+
+// reverseBytes 反转 byte slice 的顺序
+func reverseBytes(b []byte) []byte {
+	reversed := make([]byte, len(b))
+	for i := range b {
+		reversed[i] = b[len(b)-1-i]
+	}
+	return reversed
+}
+
+func gt(name string) {
+	//rintf("\n=============%s================\n", name)
+}
+
+func switchSQLType(sql string) int {
+	// 去掉首尾的空格、换行符和回车符
+	sql = strings.TrimSpace(sql)
+	if len(sql) < 6 {
+		return SQL_OTHER
+	}
+
+	// 取前6个字符并转为大写
+	kstr := strings.ToUpper(sql[:6])
+
+	// 根据SQL语句前缀判断类型
+	switch {
+	case strings.HasPrefix(kstr, "SELECT"):
+		// if strings.Contains(sql, ";") && len(sql[strings.Index(sql, ";"):]) > 5 {
+		// 	return SQL_OTHER // 多结果集
+		// }
+		return SQL_SELECT
+	case strings.HasPrefix(kstr, "INSERT"):
+		return SQL_INSERT
+	case strings.HasPrefix(kstr, "UPDATE"):
+		return SQL_UPDATE
+	case strings.HasPrefix(kstr, "DELETE"):
+		return SQL_DELETE
+	case strings.HasPrefix(kstr, "CREATE"):
+		return SQL_CREATE
+	case strings.HasPrefix(kstr, "ALTER "):
+		return SQL_ALTER
+	case strings.HasPrefix(kstr, "EXEC "):
+		return SQL_PROCEDURE
+	case strings.HasPrefix(kstr, "EXECUT"):
+		return SQL_PROCEDURE
+	case strings.HasPrefix(kstr, "STC"):
+		return SQL_SELECT
+	default:
+		return SQL_OTHER
+	}
+}
+
+// CheckEndian 判断机器的字节序
+func CheckEndian() bool {
+	var i int32 = 0x01020304
+	b := [4]byte{}
+
+	// 将整数值写入字节切片
+	binary.BigEndian.PutUint32(b[:], uint32(i))
+
+	// 判断字节序
+	if b[0] == 0x01 {
+		return false // 大端字节序
+	} else {
+		return true // 小端字节序
+	}
+}

+ 385 - 0
static/index.html

@@ -0,0 +1,385 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Card Waterfall</title>
+    <link rel="stylesheet" href="./static/styles.css">
+
+    <script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/3.2.31/vue.global.min.js"></script>
+    <style>
+      
+    </style>
+</head>
+<body>
+    <div id="app">
+        <div class="card-container">
+            <div v-for="(card, index) in cards" :key="index" class="card" @click="showCardDetails(card)">
+                <img :src="card.image" alt="Card Image">
+                <div class="title">{{ card.title }}</div>
+                <div class="content">{{ card.content }}</div>
+                <div class="date">{{ card.date }}</div>
+            </div>
+            <!-- Default Add Card -->
+            <div class="card add-card" @click="openAddCardModal">
+                <div class="add-icon">+</div>
+            </div>
+        </div>
+
+        <!--card Modal -->
+        <div class="card-modal" :class="{ active: showModal }">
+            <div class="card-modal-container">
+                <div class="card-modal-left">
+                    <button class="card-modal-close-button" @click="showModal = false">关闭</button>
+          
+                </div>
+             
+                <div class="card-modal-right">
+                    <h3>{{ modalTitle }}</h3>
+                    <div v-if="modalCard">
+                        <div v-for="(group, groupIndex) in modalCard.groups" :key="groupIndex" class="card-modal-group-box-container">
+                            <!-- 循环展示 conn_info 里的信息 -->
+                            <div v-for="(conn, connIndex) in group.conn_info" :key="connIndex" class="card-modal-group-box">
+                   
+                                <div class="state-box">节点状态</div>
+
+                                <div class="title"> <strong>节点 {{ connIndex + 1 }}</strong></div>
+                              
+                                <div class="content">{{ conn.ssh_info.host }} <strong>端口:</strong> {{ conn.ssh_info.port }}</div>
+                              
+                                <p><strong>用户:</strong> {{ conn.ssh_info.username }} <strong>密码:</strong> {{ conn.ssh_info.password }}</p>
+                      
+                                <p><strong>数据库:</strong> {{ conn.db_info.database }} <strong></strong>端口:</strong> {{ conn.db_info.port }}</p>
+                             
+                                <p><strong>用户:</strong> {{ conn.db_info.user }} <strong>密码:</strong> {{ conn.db_info.password }}</p>
+                         
+                                <div class="menu-bar">
+                                    <button @click="modifyGroup(connIndex)">修改</button>
+                                    <button @click="groupNodeDetail(conn,group.base_filename)">详情</button>
+                                    <button @click="setSystemInfo(conn,group.base_filename)">获取系统信息</button>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div v-else>
+                        <div v-for="(group, index) in formGroups" :key="index" class="group-box">
+                            <input v-model="group.ssh_ip" placeholder="服务器 IP 地址(必选项)">
+                            <input v-model="group.ssh_port" placeholder="服务器 SSH 端口 默认 22">
+                            <input v-model="group.ssh_username" placeholder="服务器 登录用户名">
+                            <input v-model="group.ssh_password" placeholder="服务器 登录密码">
+                            <input v-model="group.db_port" placeholder="数据库 端口">
+                            <input v-model="group.db_database" placeholder="数据库 库名">
+                            <input v-model="group.db_user" placeholder="数据库 用户名">
+                            <input v-model="group.db_password" placeholder="数据库 密码">
+                        </div>
+                        <div class="modal-buttons">
+                            <button @click="addGroup">新增一组</button>
+                            <button @click="removeGroup" v-if="formGroups.length > 1">删除一组</button>
+                        </div>
+                        <div class="modal-buttons">
+                            <button @click="addNewCard">添加</button>
+                            <button @click="showModal = false">取消</button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- Group Node Detail Modal 组里节点的模态框-->
+        <div class="group-node-modal" :class="{ active: showGroupModal }">
+            <div class="group-node-modal-container">
+                <div class="group-node-modal-content">
+                    <button class="group-node-modal-close-button" @click="showGroupModal = false">关闭</button>
+                    <h3>组详细信息</h3>
+                    <div v-if="currentGroup && currentGroup.systemInfo">
+                        <p><strong>节点状态:</strong> <span>{{ currentGroup.systemInfo.status  }}</span></p>
+                        
+                        <p><strong>SSH 信息:</strong></p>
+                        <p><strong>Host:</strong> <span>{{ currentGroup.systemInfo.host  }}</span></p>
+                        <p><strong>Port:</strong> <span>{{ currentGroup.systemInfo.port }}</span></p>
+                        <p><strong>Username:</strong> <span>{{ currentGroup.systemInfo.username }}</span></p>
+                        <p><strong>Password:</strong> <span>{{ currentGroup.systemInfo.password  }}</span></p>
+        
+                        <p><strong>系统信息:</strong></p>
+                        <p><strong>操作系统 (OS):</strong> <span>{{ currentGroup.systemInfo.os || '再刷新' }}</span></p>
+                        <p><strong>CPU:</strong> <span>{{ currentGroup.systemInfo.cpu || '再刷新' }}</span></p>
+                        <p><strong>内存 (Memory):</strong> <span>{{ currentGroup.systemInfo.memory || '再刷新' }}</span></p>
+                        <p><strong>磁盘 (Disk):</strong> <span>{{ currentGroup.systemInfo.disk || '再刷新' }}</span></p>
+                        <p><strong>磁盘速度 (DiskSpeed):</strong> <span>{{ currentGroup.systemInfo.diskSpeed || '再刷新' }}</span></p>
+                        <p><strong>网络 (Network):</strong> <span>{{ currentGroup.systemInfo.network || '再刷新' }}</span></p>
+                        <p><strong>网络速度 (NetworkSpeed):</strong> <span>{{ currentGroup.systemInfo.networkSpeed || '再刷新' }}</span></p>
+                        <p><strong>错误 (Error):</strong> <span>{{ currentGroup.systemInfo.error || '再刷新' }}</span></p>
+        
+
+                        <!-- 添加 "获取最新系统信息" 按钮 -->
+                        <button @click="setSystemInfo(currentGroup, group.base_filename)">获取最新系统信息</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+
+
+
+    </div>
+
+
+    <script>
+        const app = Vue.createApp({
+            data() {
+                return {
+                    cards: [],
+                    showModal: false,
+                    formGroups: [
+                        {
+                            db_database: '',
+                            db_password: '',
+                            db_port: '',
+                            db_user: '',
+                            ssh_ip: '',
+                            ssh_password: '',
+                            ssh_port: '',
+                            ssh_username: ''
+                        }
+                    ],
+                    modalCard: null,
+                    modalTitle: '',
+                    showGroupModal: false,
+                    currentGroup: null
+                };
+            },
+            mounted() {
+                this.fetchCardData();
+            },
+            methods: {
+                openAddCardModal() {
+                    this.modalCard = null;
+                    this.modalTitle = '添加新节点';
+                    this.showModal = true;
+                },
+                addGroup() {
+                    const lastGroup = this.formGroups[this.formGroups.length - 1];
+                    this.formGroups.push(Object.assign({}, lastGroup));
+                },
+                removeGroup() {
+                    if (this.formGroups.length > 1) {
+                        this.formGroups.pop();
+                    }
+                },
+                addNewCard() {
+                    console.log('开始添加新卡片,准备发送数据...');
+                    const groupsContent = this.formGroups.map(group => `数据库: ${group.db_database}, 用户: ${group.db_user}, 端口: ${group.db_port}`).join('; ');
+                    const newCardData = {
+                        image: 'https://via.placeholder.com/300',
+                        title: '数据库连接信息',
+                        content: groupsContent,
+                        date: new Date().toISOString().split('T')[0]
+                    };
+
+                    // 添加卡片到UI
+                    this.cards.push(newCardData);
+
+                    // 准备发送到服务器的数据
+                    const dataToSend = this.formGroups.map(group => ({
+                        ssh_info: {
+                            username: group.ssh_username,
+                            password: group.ssh_password,
+                            host: group.ssh_ip,
+                            port: group.ssh_port
+                        },
+                        db_info: {
+                            user: group.db_user,
+                            password: group.db_password,
+                            database: group.db_database,
+                            port: group.db_port
+                        }
+                    }));
+
+                    // 发送数据到服务器
+                    console.log('正在发送数据:', JSON.stringify(dataToSend));
+                    fetch('http://127.0.0.1:8080/api/AddConnectInfo', {
+                        method: 'POST',
+                        headers: {
+                            'Content-Type': 'application/json'
+                        },
+                        body: JSON.stringify(dataToSend)
+                    })
+                    .then(response => {
+                        if (!response.ok) {
+                            throw new Error('网络响应不正常');
+                        }
+                        return response.json();
+                    })
+                    .then(data => {
+                        console.log('数据发送成功:', data);
+                    })
+                    .catch(error => {
+                        console.error('发送数据时出错:', error);
+                    });
+
+                    // 关闭模态框并重置表单
+                    this.showModal = false;
+                    this.resetFormData();
+                },
+                resetFormData() {
+                    this.formGroups = [
+                        {
+                            db_database: '',
+                            db_password: '',
+                            db_port: '',
+                            db_user: '',
+                            ssh_ip: '',
+                            ssh_password: '',
+                            ssh_port: '',
+                            ssh_username: ''
+                        }
+                    ];
+                },
+                fetchCardData() {
+                    fetch('http://127.0.0.1:8080/api/GetConnectInfo')
+                        .then(response => response.json())
+                        .then(data => {
+                            const connectInfo = data.data;
+                            console.log("connectInfo :", connectInfo);
+                            for (let host in connectInfo) {
+                                if (Object.keys(connectInfo[host]).length === 0) continue;
+                                const groupData = connectInfo[host];
+                                const groups = [];
+                                for (let group in groupData) {
+                                    groups.push(groupData[group]);
+                                }
+                                const cardData = {
+                                    image: 'https://via.placeholder.com/300',
+                                    title: `连接信息 - ${host}`,
+                                    groups: groups,
+                                    content: `共 ${groups.length} 组连接信息`,
+                                    date: new Date().toISOString().split('T')[0]
+                                };
+                                this.cards.push(cardData);
+                            }
+                        })
+                        .catch(error => {
+                            console.error('Error fetching connect info:', error);
+                        });
+                },
+                showCardDetails(card) {
+                    console.log("showCardDetails(card)  :", card);
+                    this.modalCard = card;
+                    this.modalTitle = card.title;
+                    this.showModal = true;
+                },
+                modifyGroup(index) {
+                    alert(`修改组 ${index + 1} 的信息`);
+                },
+                groupNodeDetail(groups, base_filename) {
+                  
+                    console.log("groupNodeDetail(groups)  :", groups);
+                    this.currentGroup = groups;    // 设置当前组为选中的组
+                    this.showGroupModal = true;  // 显示组详情模态框
+                        // 构建要发送的数据
+                    const dataToSend = {
+                        base_filename: base_filename || "default_filename", // 如果没有提供 base_filename,可以使用默认值
+                        username: groups.ssh_info.username,
+                        password: groups.ssh_info.password,
+                        host: groups.ssh_info.host,
+                        port: groups.ssh_info.port
+                    };
+
+                    console.log('发送的数据:', JSON.stringify(dataToSend));
+                      // 发送 POST 请求到后端接口
+                    fetch('http://127.0.0.1:8080/api/GetSingleSystemInfo', {
+                        method: 'POST',
+                        headers: {
+                            'Content-Type': 'application/json'
+                        },
+                        body: JSON.stringify(dataToSend)
+                    })
+                    .then(response => {
+                        if (!response.ok) {
+                            throw new Error('网络响应不正常');
+                        }
+                        return response.json();
+                    })
+                    .then(data => {
+                        console.log('接收到的系统信息:', data);
+
+                        // 将返回的数据保存到 currentGroup 对象中
+                        this.currentGroup.systemInfo = {
+                            os: data.data.Os,
+                            cpu: data.data.Cpu,
+                            memory: data.data.Memory,
+                            disk: data.data.Disk,
+                            diskSpeed: data.data.DiskSpeed,
+                            network: data.data.Network,
+                            networkSpeed: data.data.NetworkSpeed,
+                            error: data.data.Error
+                        };
+                    })
+                    .catch(error => {
+                        console.error('获取系统信息时出错:', error);
+                    });
+                },
+                setSystemInfo(groups, base_filename) {
+                    console.log('正在获取系统信息,groups:', groups);
+                    const connection = groups;
+
+                    const dataToSend = [{
+                        base_filename: base_filename,
+                        username: connection.ssh_info.username,
+                        password: connection.ssh_info.password,
+                        host: connection.ssh_info.host,
+                        port: connection.ssh_info.port
+                    }];
+
+                    console.log('正在获取系统信息,发送的数据:', JSON.stringify(dataToSend));
+
+                    // 1. 发送 POST 请求启动任务
+                    fetch('http://127.0.0.1:8080/api/SetSystemInfo', {
+                        method: 'POST',
+                        headers: {
+                            'Content-Type': 'application/json'
+                        },
+                        body: JSON.stringify(dataToSend)
+                    })
+                    .then(response => {
+                        console.log('/api/SetSystemInfo POST发送的数据:', JSON.stringify(dataToSend));
+                        if (!response.ok) {
+                            throw new Error('网络响应不正常');
+                        }
+                        return response.json(); // 假设返回 JSON 格式数据,其中包含 progressID
+                    })
+                    .then(data => {
+                        const progressID = data.progressID;
+                        console.log('收到的 progressID:', progressID, data);
+
+                        // 2. 建立 SSE 长连接,发送 progressID 并接收进度更新
+                        const eventSource = new EventSource(`http://127.0.0.1:8080/api/GetSystemInfoProgress?progressID=${progressID}`);
+                        eventSource.addEventListener('progress', (event) => {
+                            const progressData = JSON.parse(event.data);
+                            console.log("进度更新:", progressData.message);
+
+                            // 获取字段名称 (name) 和数据 (output)
+                            const [name, server, output] = progressData.message.split(": ");
+
+                            // 更新 currentGroup.systemInfo 的相应字段
+                            if (this.currentGroup.systemInfo.hasOwnProperty(name)) {
+                                this.$set(this.currentGroup.systemInfo, name, output || '再刷新');
+                            }
+                        });
+
+                        eventSource.onerror = (event) => {
+                            console.error("EventSource failed:", event);
+                            eventSource.close(); // 出错时关闭连接
+                        };
+                    })
+                    .catch(error => {
+                        console.error('获取系统信息时出错:', error);
+                    });
+                }
+
+            }
+        });
+        app.mount('#app');
+    </script>
+</body>
+</html>

+ 39 - 0
static/script.js

@@ -0,0 +1,39 @@
+document.addEventListener('DOMContentLoaded', () => {
+    const waterfallContainer = document.getElementById('waterfallContainer');
+    const addCard = document.getElementById('addCard');
+
+    // Fetch data from API and render cards
+    fetchDataAndRenderCards();
+
+    // Add new group card click handler
+    addCard.addEventListener('click', () => {
+        alert('Add new group functionality will be implemented here.');
+    });
+});
+
+async function fetchDataAndRenderCards() {
+    try {
+        const response = await fetch('https://api.example.com/cards');
+        const data = await response.json();
+        renderCards(data);
+    } catch (error) {
+        console.error('Error fetching card data:', error);
+    }
+}
+
+function renderCards(data) {
+    const waterfallContainer = document.getElementById('waterfallContainer');
+
+    data.forEach(item => {
+        const card = document.createElement('div');
+        card.classList.add('card');
+
+        card.innerHTML = `
+            <img src="${item.imageUrl}" alt="${item.title}">
+            <h3>${item.title}</h3>
+            <p>${item.description}</p>
+        `;
+
+        waterfallContainer.appendChild(card);
+    });
+}

+ 315 - 0
static/styles.css

@@ -0,0 +1,315 @@
+body {
+    font-family: Arial, sans-serif;
+    background-color: #f0f0f0;
+    display: flex;
+    justify-content: center;
+    align-items: flex-start;
+    min-height: 100vh;
+    margin: 0;
+    padding-top: 20px;
+}
+
+.card-container {
+    width: 90%;
+    max-width: 1200px;
+    display: flex;
+    flex-wrap: wrap;
+    gap: 1em;
+}
+
+.card {
+    background: white;
+    border-radius: 10px;
+    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+    overflow: hidden;
+    padding: 1em;
+    text-align: center;
+    cursor: pointer;
+    width: calc(33% - 1em);
+}
+
+.add-card {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    cursor: pointer;
+    height: 150px;
+    font-size: 2em;
+    color: #888;
+    border: 2px dashed #ccc;
+    width: calc(33% - 1em);
+    order: 999;
+}
+
+.add-icon {
+    font-weight: bold;
+}
+
+.card img {
+    width: 100%;
+    height: auto;
+    border-radius: 10px;
+    margin-bottom: 0.5em;
+}
+
+.card .title {
+    font-size: 1.2em;
+    font-weight: bold;
+    margin-bottom: 0.5em;
+}
+
+.card .content {
+    font-size: 1em;
+    color: #555;
+}
+
+.card .date {
+    font-size: 0.8em;
+    color: #999;
+    margin-top: 0.5em;
+}
+
+/*点击卡片弹出的模态框*/
+.card-modal {
+    position: fixed;               /* 固定定位,使模态框相对于视口固定 */
+    top: 0;                        /* 距离视口顶部0,贴齐顶部 */
+    left: 0;                       /* 距离视口左侧0,贴齐左边 */
+    width: 100%;                   /* 宽度占满整个视口 */
+    height: 100%;                  /* 高度占满整个视口 */
+    background: rgba(0, 0, 0, 0.5);/* 半透明黑色背景,营造遮罩效果 */
+    display: flex;                 /* 使用Flex布局 */
+    visibility: hidden;            /* 初始状态下隐藏模态框 */
+}
+
+.card-modal.active {
+    visibility: visible;           /* 添加active类后,模态框可见 */
+}
+
+
+.card-modal-container {
+width: 100%;                   /* 容器宽度为父元素的90% */
+display: flex;                /* 使用Flex布局 */
+flex-wrap: wrap;              /* 允许子元素换行 */
+gap: 1em;                     /* 子元素之间的间距为1em */
+}
+
+.card-modal-left {
+    width: 10%;
+    background: #f3f3f3;
+}
+
+
+.card-modal-right {
+    width: 85%;
+    background: #f3f3f3;
+}
+
+.card-modal-group-box-container {
+margin-left: 250px;   /* 左外边距 */
+display: flex;                /* 使用Flex布局 */
+flex-wrap: wrap;              /* 允许子元素换行 */
+gap: 1em;                     /* 子元素之间的间距为1em */
+}
+
+
+/*模态框里的节点组*/
+.card-modal-group-box {
+background: white;            /* 背景颜色为白色 */
+border-radius: 10px;          /* 圆角半径为10像素 */
+box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
+overflow: hidden;             /* 隐藏超出部分内容 */
+padding: 1em;                 /* 内边距为1em */
+margin-left: 25px;              /* 左外边距 */
+margin-top: 30px; 
+text-align: center;           /* 文本居中对齐 */
+cursor: pointer;              /* 鼠标悬停时显示指针样式 */
+width: calc(18% - 1em);       /* 宽度为33%减去1em */
+}
+
+.card-modal-group-box   .state-box {
+width: 100%;                /* 设置宽度为60像素 */
+height: 90px;               /* 设置高度为90像素 */
+display: flex;              /* 使用 Flex 布局 */
+justify-content: center;    /* 水平居中对齐内容 */
+align-items: center;        /* 垂直居中对齐内容 */
+background: linear-gradient(to bottom, #f9f9f9, #eaeaea); /* 添加柔和的渐变背景 */
+border-radius: 12px;        /* 圆角半径为12像素,营造圆润效果 */
+box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* 添加细腻的阴影 */
+font-size: 16px;            /* 设置字体大小 */
+font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; /* 使用苹果常用字体 */
+color: #333;                /* 设置深灰色字体 */
+}
+
+.card-modal-group-box .title {
+font-size: 1.2em;
+font-weight: bold;
+margin-bottom: 0.5em;
+}
+.card-modal-group-box .content {
+font-size: 1em;
+color: #555;
+}
+
+/*关闭模态框按钮*/
+.card-modal-close-button {
+    position: absolute;
+    top: 10px;
+    left: 10px;
+    background: #007bff;
+    color: #fff;
+    border: none;
+    border-radius: 5px;
+    padding: 0.5em 1em;
+    cursor: pointer;
+}
+
+.menu-bar {
+  
+    background: #ffffff;
+    border: 1px solid #ccc;
+    border-radius: 5px;
+    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+    padding: 0.5em;
+    visibility: hidden;
+}
+
+.menu-bar button {
+    display: block;
+    width: 100%;
+    margin-bottom: 0.5em;
+    padding: 0.5em;
+    background: #007bff;
+    color: #fff;
+    border: none;
+    border-radius: 5px;
+    cursor: pointer;
+}
+
+.menu-bar button:last-child {
+    margin-bottom: 0;
+}
+
+.card-modal-group-box:hover .menu-bar {
+    visibility: visible;
+}
+
+.modal-content input {
+    width: 100%;
+    padding: 0.5em;
+    margin: 0.5em 0;
+    border: 1px solid #ccc;
+    border-radius: 5px;
+}
+
+.modal-buttons {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 1em;
+}
+
+
+/* 组内节点详情模态框样式 */
+.group-node-modal {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    display: none; /* 默认隐藏 */
+    justify-content: center;
+    align-items: center;
+    background-color: rgba(0, 0, 0, 0.3); /* 柔和的半透明背景 */
+    z-index: 9999;
+    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+}
+
+.group-node-modal.active {
+    display: flex; /* 显示模态框 */
+}
+
+.group-node-modal-container {
+    background-color: #f9f9f9;
+    padding: 20px;
+    border-radius: 15px;
+    width: 500px;
+    max-width: 90%;
+    max-height: 90%; /* 限制模态框的高度 */
+    overflow-y: auto; /* 超出内容时启用滚动 */
+    position: relative;
+    box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
+}
+
+/* 关闭按钮样式 */
+.group-node-modal-close-button {
+    position: absolute;
+    top: 10px;
+    right: 10px;
+    background: none;
+    border: none;
+    font-size: 18px;
+    color: #007aff;
+    cursor: pointer;
+}
+
+/* 字段容器 */
+.group-node-modal-content p {
+    background-color: #fff;
+    padding: 10px;
+    border-radius: 10px;
+    border: 1px solid #e5e5ea;
+    margin-bottom: 10px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+    font-size: 16px;
+    color: #333;
+    display: flex;
+    flex-direction: column; /* 让标签和内容垂直排列 */
+    word-break: break-word; /* 防止超长内容溢出,自动换行 */
+    overflow-wrap: break-word; /* 长词自动换行 */
+}
+
+/* 标签与内容的分隔 */
+.group-node-modal-content p strong {
+    color: #8e8e93;
+    margin-bottom: 5px;
+}
+
+/* 模态框标题 */
+.group-node-modal-content h3 {
+    margin-bottom: 15px;
+    font-size: 20px;
+    color: #333;
+    font-weight: bold;
+    text-align: center;
+}
+
+/* iOS 风格的按钮 */
+.group-node-modal-container button {
+    background-color: #007aff;
+    color: white;
+    border: none;
+    padding: 10px 15px;
+    border-radius: 10px;
+    font-size: 16px;
+    cursor: pointer;
+    margin-top: 15px;
+    width: 100%;
+    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+
+.group-node-modal-container button:hover {
+    background-color: #005dbb;
+}
+
+/* 超长文本处理 */
+.group-node-modal-content p {
+    max-height: 80px; /* 限制每个字段的最大高度 */
+    overflow: hidden;
+    text-overflow: ellipsis; /* 超出内容时添加省略号 */
+    white-space: nowrap; /* 禁止换行 */
+}
+
+.group-node-modal-content p:hover {
+    white-space: normal; /* 当鼠标悬停时显示完整内容 */
+    max-height: none; /* 取消高度限制 */
+    overflow: visible; /* 显示完整文本 */
+}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác