You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
112 lines
3.6 KiB
112 lines
3.6 KiB
package console
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
_ "github.com/go-sql-driver/mysql" // 注册 database/sql 的 "mysql" 驱动
|
|
_ "github.com/jackc/pgx/v5/stdlib" // 注册 database/sql 的 "pgx" 驱动
|
|
)
|
|
|
|
// appsTest 注册应用前的连通性自检:
|
|
// - 应用地址(base_url):HTTP 探测,用于事件通知通道(配置更新等)是否可达;
|
|
// - 业务库 DSN(service_dsn):连库 Ping,用于读取业务数据是否可访问。
|
|
//
|
|
// 两项都通过,前端才放行保存。
|
|
func (this *serverComp) appsTest(c *gin.Context) {
|
|
var req struct {
|
|
BaseUrl string `json:"base_url"`
|
|
ServiceDsn string `json:"service_dsn"`
|
|
}
|
|
_ = c.ShouldBindJSON(&req)
|
|
apiOk, apiMsg := testAPIReachable(req.BaseUrl)
|
|
dbOk, dbMsg := testDBReachable(req.ServiceDsn)
|
|
writeOK(c, gin.H{
|
|
"ok": apiOk && dbOk,
|
|
"api_ok": apiOk,
|
|
"api_msg": apiMsg,
|
|
"db_ok": dbOk,
|
|
"db_msg": dbMsg,
|
|
})
|
|
}
|
|
|
|
// checkDeployment 部署行(app_registry)落库前校验,按 Status 分流;返回非空=不通过原因。
|
|
// 会就地把空 Status 规整为 "existing"。
|
|
// - existing:把已部署的应用区域加进来 → 复检地址可达 + 业务库可连(checkAppConn)。
|
|
// - pending :选一个部署目标环境待部署 → 只需选中环境(EnvId),允许 DSN/地址暂空(部署后回填)。
|
|
func checkDeployment(m *AppRegistry) string {
|
|
if m.Status == "" {
|
|
m.Status = "existing"
|
|
}
|
|
switch m.Status {
|
|
case "pending":
|
|
if m.EnvId == 0 {
|
|
return "部署目标环境必填"
|
|
}
|
|
return ""
|
|
case "existing":
|
|
return checkAppConn(m)
|
|
default:
|
|
return "未知的部署状态: " + m.Status
|
|
}
|
|
}
|
|
|
|
// checkAppConn 保存前服务端复检:应用地址可达 + 业务库可连,任一不过返回原因;空串表示通过。
|
|
// 与前端「测试连接」同一套逻辑,防止绕过前端直接调接口写入不可用的注册项。
|
|
func checkAppConn(m *AppRegistry) string {
|
|
if ok, msg := testAPIReachable(m.BaseUrl); !ok {
|
|
return "应用地址检测失败: " + msg
|
|
}
|
|
if ok, msg := testDBReachable(m.ServiceDsn); !ok {
|
|
return "业务库检测失败: " + msg
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// testAPIReachable HTTP 探测应用地址:能建立连接拿到任意响应即视为可达(含 4xx/5xx,
|
|
// 说明服务在线,只是该路径不一定接 GET)。仅判断「主机/服务是否在线」。
|
|
func testAPIReachable(rawurl string) (bool, string) {
|
|
rawurl = strings.TrimSpace(rawurl)
|
|
if rawurl == "" {
|
|
return false, "应用地址为空"
|
|
}
|
|
if !strings.HasPrefix(rawurl, "http://") && !strings.HasPrefix(rawurl, "https://") {
|
|
return false, "地址需以 http:// 或 https:// 开头"
|
|
}
|
|
client := &http.Client{Timeout: 5 * time.Second}
|
|
resp, err := client.Get(rawurl)
|
|
if err != nil {
|
|
return false, "不可达: " + err.Error()
|
|
}
|
|
defer resp.Body.Close()
|
|
return true, "可达 (HTTP " + strconv.Itoa(resp.StatusCode) + ")"
|
|
}
|
|
|
|
// testDBReachable 用 database/sql 连业务库并 Ping(5s 超时),测完即关、不留连接。
|
|
func testDBReachable(dsn string) (bool, string) {
|
|
dsn = strings.TrimSpace(dsn)
|
|
if dsn == "" {
|
|
return false, "DSN 为空"
|
|
}
|
|
driver := "mysql"
|
|
if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") {
|
|
driver = "pgx"
|
|
}
|
|
db, err := sql.Open(driver, dsn)
|
|
if err != nil {
|
|
return false, "DSN 解析失败: " + err.Error()
|
|
}
|
|
defer db.Close()
|
|
db.SetMaxOpenConns(1)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
if err := db.PingContext(ctx); err != nil {
|
|
return false, "连接失败: " + err.Error()
|
|
}
|
|
return true, "连接成功"
|
|
}
|
|
|