From d041b2a76080baa92ea1ff7e6c62ab077b4977ec Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 28 Oct 2022 19:23:20 +0800 Subject: [PATCH] [add] setup validate db & hosting --- engine/load.go | 6 +- setup/check.go | 68 ++++++++++++++------ setup/handler.go | 144 ++++++++++++++++++++++++++++++++++++++++-- setup/install.go | 158 ++++++++++++++--------------------------------- setup/setup.go | 6 +- share/db.go | 38 +++++++++--- 6 files changed, 273 insertions(+), 147 deletions(-) diff --git a/engine/load.go b/engine/load.go index 908c9e5b..26348a8a 100644 --- a/engine/load.go +++ b/engine/load.go @@ -81,7 +81,11 @@ func Load(cfg config.Config) (err error) { } // 第二步: 建立数据库 & 会话连接 - share.DBConnect(cfg.DB) // 创建数据库连接 + err = share.DBConnect(cfg.DB) // 创建数据库连接 + if err != nil { + printErr(cfg.Mode, "DB", err) + } + // share.SessionConnect(cfg.Session) // 创建会话服务器链接 // 加载应用引擎 diff --git a/setup/check.go b/setup/check.go index 4149b243..39219153 100644 --- a/setup/check.go +++ b/setup/check.go @@ -2,10 +2,12 @@ package setup import ( "fmt" + "net" "os" "path/filepath" + "time" - "github.com/yaoapp/gou/connector" + "github.com/yaoapp/xun/capsule" "github.com/yaoapp/yao/config" ) @@ -30,30 +32,59 @@ func Check() bool { return false } -// Validate db link -func Validate() (err error) { +// ValidateHosting host ports +func ValidateHosting(option map[string]string) error { + if option["YAO_PORT"] == "" { + return fmt.Errorf("监听端口必须填写") + } - root := appRoot() - path := filepath.Join(root, "db", "yao.db") + if option["YAO_STUDIO_PORT"] == option["YAO_PORT"] { + return fmt.Errorf("监听端口和 Studio 端口不能相同") + } - data := []byte(fmt.Sprintf(`{ - "type": "sqlite3", - "options": { - "file": "%s" + if option["YAO_PORT"] != SetupPort { + conn, _ := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", option["YAO_PORT"]), time.Second) + if conn != nil { + defer conn.Close() + return fmt.Errorf("监听端口 %s 已被占用", option["YAO_PORT"]) } - }`, path)) + } - data = []byte(`{ - "type": "mysql", - "options": { - "db": "test", - "hosts": [{ "host": "127.0.0.1", "user":"root", "pass":"123456" }] + if option["YAO_STUDIO_PORT"] != SetupPort { + conn, _ := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", option["YAO_STUDIO_PORT"]), time.Second) + if conn != nil { + defer conn.Close() + return fmt.Errorf("Studio 端口 %s 已被占用", option["YAO_STUDIO_PORT"]) } - }`) + } - _, err = connector.Load(string(data), "test") - return err + return nil +} +// ValidateDB db connection +func ValidateDB(option map[string]string) error { + + driver, dsn, err := getDSN(option) + if err != nil { + return fmt.Errorf("连接失败 %s", err.Error()) + } + + m, err := capsule.Add("validate", driver, dsn) + if err != nil { + return fmt.Errorf("连接失败 %s", err.Error()) + } + + conn, err := m.Primary() + if err != nil { + return fmt.Errorf("连接失败 %s", err.Error()) + } + + err = conn.Ping(2 * time.Second) + if err != nil { + return fmt.Errorf("连接失败 %s", err.Error()) + } + + return nil } func appRoot() string { @@ -97,7 +128,6 @@ func hasInstalled(cfg config.Config) bool { } if _, err := os.Stat(dbfile); err != nil && os.IsNotExist(err) { - fmt.Println(dbfile) return false } diff --git a/setup/handler.go b/setup/handler.go index 257975ad..0cf5a69f 100644 --- a/setup/handler.go +++ b/setup/handler.go @@ -3,7 +3,9 @@ package setup import ( "fmt" "net/http" - "time" + "os" + "path/filepath" + "strings" "github.com/gin-gonic/gin" "github.com/yaoapp/kun/exception" @@ -56,7 +58,7 @@ func recovered(c *gin.Context, recovered interface{}) { // } func runSetup(c *gin.Context) { - payload := getPayload(c) + payload := getSetting(c) cfg, err := getConfig() if err != nil { @@ -99,17 +101,149 @@ func runSetup(c *gin.Context) { } func runCheck(c *gin.Context) { - time.Sleep(2 * time.Second) + + payload := getCheck(c) + dbOption, err := getDBOption(map[string]map[string]string{"db": payload}) + if err != nil { + c.JSON(500, gin.H{"code": 500, "message": err.Error()}) + return + } + + err = ValidateDB(dbOption) + if err != nil { + c.JSON(500, gin.H{"code": 500, "message": err.Error()}) + return + } + c.JSON(200, gin.H{"code": 200}) } -func getPayload(c *gin.Context) map[string]map[string]string { +func getCheck(c *gin.Context) map[string]string { + var payload map[string]string + err := c.ShouldBindJSON(&payload) + if err != nil { + c.JSON(500, gin.H{"code": 400, "message": err.Error()}) + c.Abort() + return nil + } + return payload +} + +func getSetting(c *gin.Context) map[string]map[string]string { var payload map[string]map[string]string err := c.ShouldBindJSON(&payload) if err != nil { c.JSON(500, gin.H{"code": 400, "message": err.Error()}) + c.Abort() return nil } - return payload } + +func getENVOption(payload map[string]map[string]string) (map[string]string, error) { + env, has := payload["env"] + if !has { + return nil, fmt.Errorf("缺少服务配置信息") + } + + if env["YAO_ENV"] == "开发模式(推荐)" { + env["YAO_ENV"] = "development" + } else { + env["YAO_ENV"] = "production" + } + + if env["YAO_LANG"] == "中文" { + env["YAO_LANG"] = "zh-cn" + } else { + env["YAO_LANG"] = "en-us" + } + return env, nil +} + +func getDBOption(payload map[string]map[string]string) (map[string]string, error) { + + db, has := payload["db"] + if !has { + return nil, fmt.Errorf("缺少数据库配置信息") + } + + dbOption := map[string]string{} + switch db["type"] { + case "", "sqlite", "sqlite3": + dbOption["type"] = "sqlite3" + dbOption["file"] = db["option.file"] + return dbOption, nil + + case "mysql": + dbOption["type"] = "mysql" + dbOption["db"] = db["option.db"] + dbOption["host"] = db["option.host.host"] + dbOption["port"] = db["option.host.port"] + dbOption["user"] = db["option.host.user"] + dbOption["pass"] = db["option.host.pass"] + return dbOption, nil + } + + return nil, fmt.Errorf("数据库驱动暂不支持") +} + +func getDSN(dbOption map[string]string) (string, string, error) { + + switch dbOption["type"] { + case "", "sqlite", "sqlite3": + root := appRoot() + var err error + db := filepath.Join("db", "yao.db") + if v, has := dbOption["file"]; has { + db = v + } + + if !strings.HasPrefix(db, "/") { + db = filepath.Join(root, db) + db, err = filepath.Abs(db) + if err != nil && !os.IsNotExist(err) { + return "", "", err + } + } + + dir := filepath.Dir(db) + err = os.MkdirAll(dir, os.ModePerm) + if err != nil && !os.IsExist(err) { + return "", "", err + } + + return "sqlite3", db, nil + + case "mysql": + + db := "yao" + if v, has := dbOption["db"]; has { + db = v + } + + host := "127.0.0.1" + if v, has := dbOption["host"]; has { + host = v + } + + port := "3306" + if v, has := dbOption["port"]; has { + port = v + } + + user := "root" + if v, has := dbOption["user"]; has { + user = v + } + + pass := "" + if v, has := dbOption["pass"]; has { + pass = v + } + + return "mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, pass, host, port, db), nil + } + + return "", "", fmt.Errorf("driver does not support") + +} diff --git a/setup/install.go b/setup/install.go index 7aff2715..b5c13fc3 100644 --- a/setup/install.go +++ b/setup/install.go @@ -47,55 +47,35 @@ import ( // "option.host.pass": "123456" // } // } -func Install(option map[string]map[string]string) error { +func Install(payload map[string]map[string]string) error { - env, has := option["env"] - if !has { - return fmt.Errorf("缺少服务配置信息") - } - - db, has := option["db"] - if !has { - return fmt.Errorf("缺少数据库配置信息") - } - - if env["YAO_ENV"] == "开发模式(推荐)" { - env["YAO_ENV"] = "development" - } else { - env["YAO_ENV"] = "production" - } - - if env["YAO_LANG"] == "中文" { - env["YAO_LANG"] = "zh-cn" - } else { - env["YAO_LANG"] = "en-us" - } - - dbName := db["type"] - dbOption := map[string]string{} - switch dbName { - case "", "sqlite", "sqlite3": - dbName = "sqlite3" - dbOption["file"] = db["option.file"] - break - - case "mysql": - dbName = "mysql" - dbOption["db"] = db["option.db"] - dbOption["host"] = db["option.host.host"] - dbOption["port"] = db["option.host.port"] - dbOption["user"] = db["option.host.user"] - dbOption["pass"] = db["option.host.pass"] - break - } - - root := appRoot() - err := makeService(root, "0.0.0.0", env["YAO_PORT"], env["YAO_STUDIO_PORT"], env["YAO_LANG"]) + dbOption, err := getDBOption(payload) if err != nil { return err } - err = makeDB(root, dbName, dbOption) + err = ValidateDB(dbOption) + if err != nil { + return err + } + + envOption, err := getENVOption(payload) + if err != nil { + return err + } + + err = ValidateHosting(envOption) + if err != nil { + return err + } + + root := appRoot() + err = makeService(root, "0.0.0.0", envOption["YAO_PORT"], envOption["YAO_STUDIO_PORT"], envOption["YAO_LANG"]) + if err != nil { + return err + } + + err = makeDB(root, dbOption) if err != nil { return err } @@ -145,85 +125,32 @@ func makeService(root string, host string, port string, studioPort string, lang return err } + err = envSet(file, "YAO_STUDIO_PORT", studioPort) + if err != nil { + return err + } + return envSet(file, "YAO_LANG", lang) } -func makeDB(root string, driver string, option map[string]string) error { +func makeDB(root string, option map[string]string) error { + driver, dsn, err := getDSN(option) if driver != "mysql" && driver != "sqlite3" { return fmt.Errorf("数据库驱动应该为: mysql/sqlite3") } file := filepath.Join(root, ".env") - - dsn := "" - switch driver { - case "mysql": - - db := "yao" - if v, has := option["db"]; has { - db = v - } - - host := "127.0.0.1" - if v, has := option["host"]; has { - host = v - } - - port := "3306" - if v, has := option["port"]; has { - port = v - } - - user := "root" - if v, has := option["user"]; has { - user = v - } - - pass := "" - if v, has := option["pass"]; has { - pass = v - } - - err := envSet(file, "YAO_DB_DRIVER", driver) - if err != nil { - return err - } - - dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, pass, host, port, db) - return envSet(file, "YAO_DB_PRIMARY", dsn) - - case "sqlite", "sqlite3": - - var err error - db := filepath.Join("db", "yao.db") - if v, has := option["db"]; has { - file = v - } - - if !strings.HasPrefix(db, "/") { - db = filepath.Join(root, db) - db, err = filepath.Abs(db) - if err != nil && !os.IsNotExist(err) { - return err - } - } - - dir := filepath.Dir(db) - err = os.MkdirAll(dir, os.ModePerm) - if err != nil && !os.IsExist(err) { - return err - } - - err = envSet(file, "YAO_DB_DRIVER", "sqlite3") - if err != nil { - return err - } - - return envSet(file, "YAO_DB_PRIMARY", db) + if err != nil { + return err } - return fmt.Errorf("数据库驱动应该为: mysql/sqlite3") + err = envSet(file, "YAO_DB_DRIVER", driver) + if err != nil { + return err + } + + return envSet(file, "YAO_DB_PRIMARY", dsn) } func makeSession(root string) error { @@ -246,7 +173,7 @@ func makeSession(root string) error { return err } - ssfile := filepath.Join(root, "data", ".session") + ssfile := filepath.Join(root, "db", ".session") return envSet(file, "YAO_SESSION_FILE", ssfile) } @@ -296,6 +223,11 @@ func makeDirs(root string) error { return err } + err = os.MkdirAll(filepath.Join(root, "db"), os.ModePerm) + if err != nil && !os.IsExist(err) { + return err + } + return nil } diff --git a/setup/setup.go b/setup/setup.go index 5af2f869..770953ec 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -19,6 +19,9 @@ import ( "github.com/yaoapp/yao/widgets/app" ) +// SetupPort setup port +var SetupPort string = "5099" + // XGenSetupServer XGen Setup var XGenSetupServer http.Handler = http.FileServer(data.Setup()) @@ -75,7 +78,7 @@ func Start() (err error) { router.POST("/api/__yao/app/setup", runSetup) // Server setting - addr := ":5099" + addr := fmt.Sprintf(":%s", SetupPort) // Listen l, err := net.Listen("tcp4", addr) @@ -199,6 +202,7 @@ func welcome(l net.Listener) { } fmt.Println() + SetupPort = port } func printError(message string, args ...interface{}) { diff --git a/share/db.go b/share/db.go index d59bab8b..b5719c40 100644 --- a/share/db.go +++ b/share/db.go @@ -1,25 +1,47 @@ package share import ( + "fmt" "time" + "github.com/yaoapp/kun/log" "github.com/yaoapp/xun/capsule" "github.com/yaoapp/yao/config" ) // DBConnect 建立数据库连接 -func DBConnect(dbconfig config.DBConfig) { +func DBConnect(dbconfig config.DBConfig) (err error) { - // 连接主库 + if dbconfig.Primary == nil { + return fmt.Errorf("YAO_DB_PRIMARY was not set") + } + + manager := capsule.New() for i, dsn := range dbconfig.Primary { - db := capsule.AddConn("primary", dbconfig.Driver, dsn, 5*time.Second) - if i == 0 { - db.SetAsGlobal() + _, err = manager.Add(fmt.Sprintf("primary-%d", i), dbconfig.Driver, dsn, false) + if err != nil { + return err } } - // 连接从库 - for _, dsn := range dbconfig.Secondary { - capsule.AddReadConn("secondary", dbconfig.Driver, dsn, 5*time.Second) + if dbconfig.Secondary != nil { + for i, dsn := range dbconfig.Secondary { + _, err = manager.Add(fmt.Sprintf("secondary-%d", i), dbconfig.Driver, dsn, true) + if err != nil { + return err + } + } } + + manager.SetAsGlobal() + go func() { + for _, c := range manager.Pool.Primary { + err = c.Ping(5 * time.Second) + if err != nil { + log.Error("%s error %v", c.Config.Name, err.Error()) + } + } + }() + + return err }