From 1b8994a63ca5ab2c514940f3364be1ff5797846b Mon Sep 17 00:00:00 2001 From: zhangchenhao Date: Thu, 15 May 2025 11:24:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dssh=E5=AF=86=E9=92=A5?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E3=80=81=E6=96=B0=E5=A2=9E=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E5=92=8C=E9=83=A8=E7=BD=B2=E5=88=B0btwaf?= =?UTF-8?q?=E3=80=81=E6=96=B0=E5=A2=9E=E7=99=BE=E5=BA=A6=E4=BA=91dns?= =?UTF-8?q?=E3=80=81=E6=96=B0=E5=A2=9E=E5=8F=AF=E9=80=89=E6=8B=A9=E6=98=AF?= =?UTF-8?q?=E5=90=A6=E9=87=8D=E5=A4=8D=E9=83=A8=E7=BD=B2=E3=80=81=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/cert/deploy/btpanel.go | 2 +- backend/internal/cert/deploy/btwaf.go | 201 +++++++++++++++++++++ backend/internal/cert/deploy/btwaf_test.go | 42 +++++ backend/internal/cert/deploy/deploy.go | 6 + backend/internal/cert/deploy/ssh.go | 77 +++++++- backend/internal/workflow/executor.go | 79 +++++++- backend/internal/workflow/workflow.go | 1 + backend/migrations/init.go | 43 +++-- backend/public/utils.go | 23 +++ 9 files changed, 456 insertions(+), 18 deletions(-) create mode 100644 backend/internal/cert/deploy/btwaf.go create mode 100644 backend/internal/cert/deploy/btwaf_test.go diff --git a/backend/internal/cert/deploy/btpanel.go b/backend/internal/cert/deploy/btpanel.go index d689e45..f14751d 100644 --- a/backend/internal/cert/deploy/btpanel.go +++ b/backend/internal/cert/deploy/btpanel.go @@ -79,7 +79,7 @@ func RequestBt(data *url.Values, method, providerID, requestUrl string) (map[str var res map[string]interface{} err = json.Unmarshal(body, &res) if err != nil { - return nil, fmt.Errorf("返回值解析失败: %v", err) + return nil, fmt.Errorf("返回值解析失败: %v,%s", err, string(body)) } if res["status"] != nil && !res["status"].(bool) { diff --git a/backend/internal/cert/deploy/btwaf.go b/backend/internal/cert/deploy/btwaf.go new file mode 100644 index 0000000..ae498a2 --- /dev/null +++ b/backend/internal/cert/deploy/btwaf.go @@ -0,0 +1,201 @@ +package deploy + +import ( + "ALLinSSL/backend/internal/access" + "bytes" + "crypto/md5" + "crypto/tls" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "time" +) + +func bfWafToken(timestamp, apiKey string) string { + keyMd5 := md5.Sum([]byte(apiKey)) + keyMd5Hex := strings.ToLower(hex.EncodeToString(keyMd5[:])) + + signMd5 := md5.Sum([]byte(timestamp + keyMd5Hex)) + signMd5Hex := strings.ToLower(hex.EncodeToString(signMd5[:])) + return signMd5Hex +} + +func RequestBtWaf(data *map[string]any, method, providerID, requestUrl string) (map[string]any, error) { + providerData, err := access.GetAccess(providerID) + if err != nil { + return nil, err + } + providerConfigStr, ok := providerData["config"].(string) + if !ok { + return nil, fmt.Errorf("api配置错误") + } + // 解析 JSON 配置 + var providerConfig map[string]string + err = json.Unmarshal([]byte(providerConfigStr), &providerConfig) + if err != nil { + return nil, err + } + + parsedURL, err := url.Parse(providerConfig["url"]) + if err != nil { + return nil, err + } + baseURL := fmt.Sprintf("%s://%s/", parsedURL.Scheme, parsedURL.Host) + + jsonData, err := json.Marshal(data) + req, err := http.NewRequest(method, baseURL+requestUrl, bytes.NewReader(jsonData)) + if err != nil { + return nil, err + } + + timestamp := time.Now().Unix() + token := bfWafToken(fmt.Sprintf("%d", timestamp), providerConfig["api_key"]) + req.Header.Set("waf_request_time", fmt.Sprintf("%d", timestamp)) + req.Header.Set("waf_request_token", token) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36") + // 自定义 Transport,跳过 SSL 证书验证 + ignoreSsl := false + if providerConfig["ignore_ssl"] == "1" { + ignoreSsl = true + } + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl}, + } + + client := &http.Client{Transport: tr} + resp, err := client.Do(req) + if err != nil { + // fmt.Println(err) + return nil, fmt.Errorf("请求BTWAF失败: %v", err) + } + body, _ := io.ReadAll(resp.Body) + defer resp.Body.Close() + var res map[string]interface{} + err = json.Unmarshal(body, &res) + if err != nil { + return nil, fmt.Errorf("返回值解析失败: %v", err) + } + if res["code"] != nil && res["code"].(float64) != 0 { + return nil, fmt.Errorf("请求出错: %s", res["res"].(string)) + } + return res, nil +} + +func GetBTWafSiteList(page int, pageSize int, siteName string, providerId string) ([]any, error) { + data := map[string]any{ + "p": page, + "p_size": pageSize, + "site_name": siteName, + } + response, err := RequestBtWaf(&data, "POST", providerId, "api/wafmastersite/get_site_list") + res := response["res"].(map[string]any) + if err != nil { + return nil, err + } + + return res["list"].([]any), nil +} + +// btwaf不支持通过API设置SSL +func DeployBtWaf(cfg map[string]any) error { + cert, ok := cfg["certificate"].(map[string]any) + if !ok { + return fmt.Errorf("证书不存在") + } + // 设置证书 + keyPem, ok := cert["key"].(string) + if !ok { + return fmt.Errorf("证书错误:key") + } + certPem, ok := cert["cert"].(string) + if !ok { + return fmt.Errorf("证书错误:cert") + } + var providerID string + switch v := cfg["provider_id"].(type) { + case float64: + providerID = strconv.Itoa(int(v)) + case string: + providerID = v + default: + return fmt.Errorf("参数错误:provider_id") + } + + data := map[string]any{ + "certContent": certPem, + "keyContent": keyPem, + } + _, err := RequestBtWaf(&data, "POST", providerID, "api/config/set_cert") + if err != nil { + return fmt.Errorf("证书部署失败: %v", err) + } + return nil +} + +func DeployBtWafSite(cfg map[string]any) error { + cert, ok := cfg["certificate"].(map[string]any) + if !ok { + return fmt.Errorf("证书不存在") + } + // 设置证书 + keyPem, ok := cert["key"].(string) + if !ok { + return fmt.Errorf("证书错误:key") + } + certPem, ok := cert["cert"].(string) + if !ok { + return fmt.Errorf("证书错误:cert") + } + var providerID string + switch v := cfg["provider_id"].(type) { + case float64: + providerID = strconv.Itoa(int(v)) + case string: + providerID = v + default: + return fmt.Errorf("参数错误:provider_id") + } + siteName, ok := cfg["siteName"].(string) + if !ok { + return fmt.Errorf("参数错误:siteName") + } + + siteId := "" + sitelist, err := GetBTWafSiteList(1, 100, siteName, providerID) + if len(sitelist) != 0 && err == nil { + for _, site := range sitelist { + siteInfo := site.(map[string]any) + if siteName == siteInfo["site_name"].(string) { + siteId = siteInfo["site_id"].(string) + } + } + } + if siteId == "" { + return fmt.Errorf("宝塔WAF找不到网站名称:%s", siteName) + } + + data := map[string]any{ + "site_id": siteId, + "types": "openCert", + "server": map[string]any{ + "listen_ssl_port": []string{"443"}, + "ssl": map[string]any{ + "full_chain": certPem, + "private_key": keyPem, + "is_ssl": 1, + }, + }, + } + _, err = RequestBtWaf(&data, "POST", providerID, "api/wafmastersite/modify_site") + + if err != nil { + return fmt.Errorf("证书部署失败: %v", err) + } + return nil +} diff --git a/backend/internal/cert/deploy/btwaf_test.go b/backend/internal/cert/deploy/btwaf_test.go new file mode 100644 index 0000000..463f304 --- /dev/null +++ b/backend/internal/cert/deploy/btwaf_test.go @@ -0,0 +1,42 @@ +package deploy + +import ( + "fmt" + "testing" +) + +func TestBTWAFSite(t *testing.T) { + cfg := map[string]any{ + "siteName": "xxx.cn", + "provider_id": "1", + "certificate": map[string]any{ + "key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAxIjmAi/paC2OmG7nOqZ+OJx7spDrx7yZiWvn1XgLW/5ODONh\nWhMT6W+cx0WMC80yCRm5JshIIMzmMxN03pRD1h4u1fPNUnJmGtthRZIm3aU7TlSM\n4tz/Zh8a3kVyN4MtWDmV1/1MV8H0YBtT6K2gxZ7Fz/YKhVATdh8Fy+1qEz3gSrw1\nz6qqEDcM8FtHoAXAdxQBkS8xu34SIriwZiN2YlrtL8Qy73j4XiJLh2cc/NPp+mW9\ncMY1cCEBxpwQTJiJHbX9LcEqYgOkkhWIijW2dYlCLaLsnvJw0TCRd6PooR8XK7MU\nS89+DsixFf3HL+iWjr6yVnQ/mAGVPQ+HD4pwmQIDAQABAoIBAALpcFb59MBZZHJ3\nui9RRi96ig6kPQoRjkjN83pjM+/h/bANMmUOQU5FHBKLwj5uhN5Dpk2fzAnIX2TE\nVgfyNGsYuWLsIM+m6EJfm7pXJwJDr3RCpm+6DIKr1U8TwlR2OhbDi6fOlfH66q79\n2Klq4SXsa0vgfllpTVCDtydFVjwAuQV7Cf6DGRjbNpN3DPLeOC1wYFimNZwudSK0\nf8grWpPFXw2TPaf3TgeBGxwL7GCTYSKT+Eq9USbhG4RArrM9oQt+h7rzaH2bFEdg\n7tOM4KIgV+aw8r0TsYisDG9dfiHfHr5vQnkmWgt/rxAOvHlJ7/64pBVuET1ZF0mB\nP6gu4Y0CgYEAzkwXvfnHI5qx9BVP6e9lGrpWrm0RxCKr2iCCwrOVALbX1yfKCb5L\nrP/jSERMuLt6bIKg/AoVu9ogCTGzntyHTbZXFGg/y5Xoul+1af2arQ1rGZ7A/Im7\nnteZePg2U6UiDRy07F94FF5aL/v97D4BffiSA+0atlgH6tpKyYfY6NsCgYEA8+Ku\nGQqX9kHDd5bbzPhLelNmHVnAjnMaHEhvzVtBA737F10Oqg9wyffqe/i/DvdUSx9r\nafKGUfzB2vVZjz//OpSQ8VhRzDTiyelKLsSTmzOokLBnwayyTxw85o9EDvTNrzfb\nYQbAjmAXWmnv5Xvx1KfvTaKFY3BmHsKYJDzwnJsCgYBK1SVjn2CSVMIqlTSI2nMl\nb+STnzLrn9wQ4uwr7nKlcK34+RD72dCfr67lfwkJldBB3lzBMHNT0jr+us26Waqn\nEPaji3Fgyz9BpAgtq3XZQl3QTFsbAGdTpkegrwEd9G/Wq8whVjw7v0Id193zPUbT\nSEDHNdITxPkSQx8P3bxcMwKBgQDO5EGk5KO9OFTFoqib3RbKku1RgM4lCefgjmKp\n5vvkXMohK8RA6BBahYHZ4U7TN2W+xMyueBsSekVJplFvgG7YFyhOVQovHb42Yz2X\nJxPA2bXp6HxchFBPZDkVrfuiZHIIbm4ghUXcgg/Nl4j3OIoSSNRtG63kiXlYJuRB\n+aB0eQKBgD79VrREpbOMS7HRlDTtfkDN94HY3T4MLErs26z/NLO/dC44tmBJGo2P\ngcQ+p7XxNjpWUnUbEiuz4R3Xgh6ULwuSseWtcQicolPHTkBjnc+6BEpyguZJ+FPZ\nGls3g3LxjGhdPlyd37CaWDvx/Jtjrd4Y9iGkGO2d9fXZD0Hg0ymX\n-----END RSA PRIVATE KEY-----", + "cert": "-----BEGIN CERTIFICATE-----\nMIIG5DCCBMygAwIBAgIQBPQGlt81+4RKt3RAFXPvrjANBgkqhkiG9w0BAQsFADBb\nMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywg\nSW5jLjElMCMGA1UEAxMcVHJ1c3RBc2lhIERWIFRMUyBSU0EgQ0EgMjAyNTAeFw0y\nNTA0MjIwMDAwMDBaFw0yNTA3MjAyMzU5NTlaMB8xHTAbBgNVBAMTFGFsbGluc3Ns\nLnphY2h5YW5nLmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxIjm\nAi/paC2OmG7nOqZ+OJx7spDrx7yZiWvn1XgLW/5ODONhWhMT6W+cx0WMC80yCRm5\nJshIIMzmMxN03pRD1h4u1fPNUnJmGtthRZIm3aU7TlSM4tz/Zh8a3kVyN4MtWDmV\n1/1MV8H0YBtT6K2gxZ7Fz/YKhVATdh8Fy+1qEz3gSrw1z6qqEDcM8FtHoAXAdxQB\nkS8xu34SIriwZiN2YlrtL8Qy73j4XiJLh2cc/NPp+mW9cMY1cCEBxpwQTJiJHbX9\nLcEqYgOkkhWIijW2dYlCLaLsnvJw0TCRd6PooR8XK7MUS89+DsixFf3HL+iWjr6y\nVnQ/mAGVPQ+HD4pwmQIDAQABo4IC3jCCAtowHwYDVR0jBBgwFoAUtBIopbTAHZ8p\ncWk82RGWSnVpUMAwHQYDVR0OBBYEFHqqdlMVBlcadf7iJLJoLnLZ7h4tMB8GA1Ud\nEQQYMBaCFGFsbGluc3NsLnphY2h5YW5nLmNuMD4GA1UdIAQ3MDUwMwYGZ4EMAQIB\nMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNV\nHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHkGCCsG\nAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29t\nMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vVHJ1c3RB\nc2lhRFZUTFNSU0FDQTIwMjUuY3J0MAwGA1UdEwEB/wQCMAAwggF9BgorBgEEAdZ5\nAgQCBIIBbQSCAWkBZwB2ABLxTjS9U3JMhAYZw48/ehP457Vih4icbTAFhOvlhiY6\nAAABll0w/o0AAAQDAEcwRQIgd24jCPm+fbHq3grMIxtvQhzkv7dvYPM/BGjPEsy1\nQ70CIQC5jXADjBh+dH50T+atn3lktBEqQhedOl6cAaP/XXmk6gB2AO08S9boBsKk\nogBX28sk4jgB31Ev7cSGxXAPIN23Pj/gAAABll0w/rUAAAQDAEcwRQIgU2GDVEH1\ns5i/RC1RhqvJjn72PAZOlDtJyLdg29vC9HECIQCj78GATYK5quitLxbn3HvD8BeT\noOz+3tacgyN6+TdvugB1AKRCxQZJYGFUjw/U6pz7ei0mRU2HqX8v30VZ9idPOoRU\nAAABll0w/sYAAAQDAEYwRAIgCvU/iBRPKoJLjmU4edBYObWAO/aJp2mWnfJ4ieAr\nrXsCIBsAppYu28h8YEOl0N9yEeF9G05IMxwkCjZKonQs2SKMMA0GCSqGSIb3DQEB\nCwUAA4ICAQB3wFou51Qvl4apMhencuQUnWF3UpYP49e0WQ72DVT3pYjYsozkSuqb\nQZcwMB6HDoHdFicxvQ/yxKyTu/nw3rXjUWYuSxXYd7lJcQ/R0tR00m6AFeinY4Aq\nq4QqoA+lriK1XqO5MomAL4FbSysT1ow/gaG9pYuXEdT4pr05I/NumjXdkwBRZOd4\nrhol2grKf3y37Qla5hUbbG3ab9nf/csJSWkCoESeXr3MB1oAU/aL9pGSagvMXSKQ\nsFs2cn2Fi8ZmJPJXIP114lgvFuFDO+C1yTNbHap/FufvAKGryfPDuPecCF6FSXej\n+bwg4/BNz5lcHbNo2XXjLgoPg4VE6mG/SQQZQEDBk5DowwMVMvh77t9RBNrHozah\nHGtQz2hCuIX7rZQYnSlvW8T75FhI/Sd+HEfU/iyTIELXBUjypnK2bOJL7+jE7f79\nuljhXlCcP52fGHCjexNBz5gIZr82KVxsfxKuZjfioPkhmWleVNMdMWYJRXu618E6\nNtNjUVsDCuMOOMNs1qScqxOT60MeDZLX+vnC93fdd/t2hLEAWWNNMkWeX2qLCE1q\nGarop9U1mJpiBWkW5cBiqnNIbhuV2fcwFIR8mVT5f1Qcw+WxE2nEjY2h75bKv8T5\n3RBngmaX8PcyLAP2s0/4UyzAnMYfioJBh37VpUYBrdriBkRds/AMZw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFnjCCBIagAwIBAgIQCSYyO0lk42hGFRLe8aXVLDANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\nMjAeFw0yNTAxMDgwMDAwMDBaFw0zNTAxMDcyMzU5NTlaMFsxCzAJBgNVBAYTAkNO\nMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSUwIwYDVQQD\nExxUcnVzdEFzaWEgRFYgVExTIFJTQSBDQSAyMDI1MIICIjANBgkqhkiG9w0BAQEF\nAAOCAg8AMIICCgKCAgEA0fuEmuBIsN6ZZVq+gRobMorOGIilTCIfQrxNpR8FUZ9R\n/GfbiekbiIKphQXEZ7N1uBnn6tXUuZ32zl6jPkZpHzN/Bmgk1BWSIzVc0npMzrWq\n/hrbk5+KddXJdsNpeG1+Q8lc8uVMBrztnxaPb7Rh7yQCsMrcO4hgVaqLJWkVvEfW\nULtoCHQnNaj4IroG6VxQf1oArQ8bPbwpI02lieSahRa78FQuXdoGVeQcrkhtVjZs\nON98vq5fPWZX2LFv7e5J6P9IHbzvOl8yyQjv+2/IOwhNSkaXX3bI+//bqF9XW/p7\n+gsUmHiK5YsvLjmXcvDmoDEGrXMzgX31Zl2nJ+umpRbLjwP8rxYIUsKoEwEdFoto\nAid59UEBJyw/GibwXQ5xTyKD/N6C8SFkr1+myOo4oe1UB+YgvRu6qSxIABo5kYdX\nFodLP4IgoVJdeUFs1Usa6bxYEO6EgMf5lCWt9hGZszvXYZwvyZGq3ogNXM7eKyi2\n20WzJXYMmi9TYFq2Fa95aZe4wki6YhDhhOO1g0sjITGVaB73G+JOCI9yJhv6+REN\nD40ZpboUHE8JNgMVWbG1isAMVCXqiADgXtuC+tmJWPEH9cR6OuJLEpwOzPfgAbnn\n2MRu7Tsdr8jPjTPbD0FxblX1ydW3RG30vwLF5lkTTRkHG9epMgpPMdYP7nY/08MC\nAwEAAaOCAVYwggFSMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLQSKKW0\nwB2fKXFpPNkRlkp1aVDAMB8GA1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485\nMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw\ndgYIKwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy\ndC5jb20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E\naWdpQ2VydEdsb2JhbFJvb3RHMi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDov\nL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDARBgNV\nHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQELBQADggEBAJ4a3svh316GY2+Z7EYx\nmBIsOwjJSnyoEfzx2T699ctLLrvuzS79Mg3pPjxSLlUgyM8UzrFc5tgVU3dZ1sFQ\nI4RM+ysJdvIAX/7Yx1QbooVdKhkdi9X7QN7yVkjqwM3fY3WfQkRTzhIkM7mYIQbR\nr+y2Vkju61BLqh7OCRpPMiudjEpP1kEtRyGs2g0aQpEIqKBzxgitCXSayO1hoO6/\n71ts801OzYlqYW9OQQQ2GCJyFbD6XHDjdpn+bWUxTKWaMY0qedSCbHE3Kl2QEF0C\nynZ7SbC03yR+gKZQDeTXrNP1kk5Qhe7jSXgw+nhbspe0q/M1ZcNCz+sPxeOwdCcC\ngJE=\n-----END CERTIFICATE-----", + "issuer": "cert-issuer", + }, + } + err := DeployBtWafSite(cfg) + fmt.Println(err) +} + +//func TestBTWAFP(t *testing.T) { +// cfg := map[string]any{ +// "provider_id": "1", +// "certificate": map[string]any{ +// "key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAxIjmAi/paC2OmG7nOqZ+OJx7spDrx7yZiWvn1XgLW/5ODONh\nWhMT6W+cx0WMC80yCRm5JshIIMzmMxN03pRD1h4u1fPNUnJmGtthRZIm3aU7TlSM\n4tz/Zh8a3kVyN4MtWDmV1/1MV8H0YBtT6K2gxZ7Fz/YKhVATdh8Fy+1qEz3gSrw1\nz6qqEDcM8FtHoAXAdxQBkS8xu34SIriwZiN2YlrtL8Qy73j4XiJLh2cc/NPp+mW9\ncMY1cCEBxpwQTJiJHbX9LcEqYgOkkhWIijW2dYlCLaLsnvJw0TCRd6PooR8XK7MU\nS89+DsixFf3HL+iWjr6yVnQ/mAGVPQ+HD4pwmQIDAQABAoIBAALpcFb59MBZZHJ3\nui9RRi96ig6kPQoRjkjN83pjM+/h/bANMmUOQU5FHBKLwj5uhN5Dpk2fzAnIX2TE\nVgfyNGsYuWLsIM+m6EJfm7pXJwJDr3RCpm+6DIKr1U8TwlR2OhbDi6fOlfH66q79\n2Klq4SXsa0vgfllpTVCDtydFVjwAuQV7Cf6DGRjbNpN3DPLeOC1wYFimNZwudSK0\nf8grWpPFXw2TPaf3TgeBGxwL7GCTYSKT+Eq9USbhG4RArrM9oQt+h7rzaH2bFEdg\n7tOM4KIgV+aw8r0TsYisDG9dfiHfHr5vQnkmWgt/rxAOvHlJ7/64pBVuET1ZF0mB\nP6gu4Y0CgYEAzkwXvfnHI5qx9BVP6e9lGrpWrm0RxCKr2iCCwrOVALbX1yfKCb5L\nrP/jSERMuLt6bIKg/AoVu9ogCTGzntyHTbZXFGg/y5Xoul+1af2arQ1rGZ7A/Im7\nnteZePg2U6UiDRy07F94FF5aL/v97D4BffiSA+0atlgH6tpKyYfY6NsCgYEA8+Ku\nGQqX9kHDd5bbzPhLelNmHVnAjnMaHEhvzVtBA737F10Oqg9wyffqe/i/DvdUSx9r\nafKGUfzB2vVZjz//OpSQ8VhRzDTiyelKLsSTmzOokLBnwayyTxw85o9EDvTNrzfb\nYQbAjmAXWmnv5Xvx1KfvTaKFY3BmHsKYJDzwnJsCgYBK1SVjn2CSVMIqlTSI2nMl\nb+STnzLrn9wQ4uwr7nKlcK34+RD72dCfr67lfwkJldBB3lzBMHNT0jr+us26Waqn\nEPaji3Fgyz9BpAgtq3XZQl3QTFsbAGdTpkegrwEd9G/Wq8whVjw7v0Id193zPUbT\nSEDHNdITxPkSQx8P3bxcMwKBgQDO5EGk5KO9OFTFoqib3RbKku1RgM4lCefgjmKp\n5vvkXMohK8RA6BBahYHZ4U7TN2W+xMyueBsSekVJplFvgG7YFyhOVQovHb42Yz2X\nJxPA2bXp6HxchFBPZDkVrfuiZHIIbm4ghUXcgg/Nl4j3OIoSSNRtG63kiXlYJuRB\n+aB0eQKBgD79VrREpbOMS7HRlDTtfkDN94HY3T4MLErs26z/NLO/dC44tmBJGo2P\ngcQ+p7XxNjpWUnUbEiuz4R3Xgh6ULwuSseWtcQicolPHTkBjnc+6BEpyguZJ+FPZ\nGls3g3LxjGhdPlyd37CaWDvx/Jtjrd4Y9iGkGO2d9fXZD0Hg0ymX\n-----END RSA PRIVATE KEY-----", +// "cert": "-----BEGIN CERTIFICATE-----\nMIIG5DCCBMygAwIBAgIQBPQGlt81+4RKt3RAFXPvrjANBgkqhkiG9w0BAQsFADBb\nMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywg\nSW5jLjElMCMGA1UEAxMcVHJ1c3RBc2lhIERWIFRMUyBSU0EgQ0EgMjAyNTAeFw0y\nNTA0MjIwMDAwMDBaFw0yNTA3MjAyMzU5NTlaMB8xHTAbBgNVBAMTFGFsbGluc3Ns\nLnphY2h5YW5nLmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxIjm\nAi/paC2OmG7nOqZ+OJx7spDrx7yZiWvn1XgLW/5ODONhWhMT6W+cx0WMC80yCRm5\nJshIIMzmMxN03pRD1h4u1fPNUnJmGtthRZIm3aU7TlSM4tz/Zh8a3kVyN4MtWDmV\n1/1MV8H0YBtT6K2gxZ7Fz/YKhVATdh8Fy+1qEz3gSrw1z6qqEDcM8FtHoAXAdxQB\nkS8xu34SIriwZiN2YlrtL8Qy73j4XiJLh2cc/NPp+mW9cMY1cCEBxpwQTJiJHbX9\nLcEqYgOkkhWIijW2dYlCLaLsnvJw0TCRd6PooR8XK7MUS89+DsixFf3HL+iWjr6y\nVnQ/mAGVPQ+HD4pwmQIDAQABo4IC3jCCAtowHwYDVR0jBBgwFoAUtBIopbTAHZ8p\ncWk82RGWSnVpUMAwHQYDVR0OBBYEFHqqdlMVBlcadf7iJLJoLnLZ7h4tMB8GA1Ud\nEQQYMBaCFGFsbGluc3NsLnphY2h5YW5nLmNuMD4GA1UdIAQ3MDUwMwYGZ4EMAQIB\nMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNV\nHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHkGCCsG\nAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29t\nMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vVHJ1c3RB\nc2lhRFZUTFNSU0FDQTIwMjUuY3J0MAwGA1UdEwEB/wQCMAAwggF9BgorBgEEAdZ5\nAgQCBIIBbQSCAWkBZwB2ABLxTjS9U3JMhAYZw48/ehP457Vih4icbTAFhOvlhiY6\nAAABll0w/o0AAAQDAEcwRQIgd24jCPm+fbHq3grMIxtvQhzkv7dvYPM/BGjPEsy1\nQ70CIQC5jXADjBh+dH50T+atn3lktBEqQhedOl6cAaP/XXmk6gB2AO08S9boBsKk\nogBX28sk4jgB31Ev7cSGxXAPIN23Pj/gAAABll0w/rUAAAQDAEcwRQIgU2GDVEH1\ns5i/RC1RhqvJjn72PAZOlDtJyLdg29vC9HECIQCj78GATYK5quitLxbn3HvD8BeT\noOz+3tacgyN6+TdvugB1AKRCxQZJYGFUjw/U6pz7ei0mRU2HqX8v30VZ9idPOoRU\nAAABll0w/sYAAAQDAEYwRAIgCvU/iBRPKoJLjmU4edBYObWAO/aJp2mWnfJ4ieAr\nrXsCIBsAppYu28h8YEOl0N9yEeF9G05IMxwkCjZKonQs2SKMMA0GCSqGSIb3DQEB\nCwUAA4ICAQB3wFou51Qvl4apMhencuQUnWF3UpYP49e0WQ72DVT3pYjYsozkSuqb\nQZcwMB6HDoHdFicxvQ/yxKyTu/nw3rXjUWYuSxXYd7lJcQ/R0tR00m6AFeinY4Aq\nq4QqoA+lriK1XqO5MomAL4FbSysT1ow/gaG9pYuXEdT4pr05I/NumjXdkwBRZOd4\nrhol2grKf3y37Qla5hUbbG3ab9nf/csJSWkCoESeXr3MB1oAU/aL9pGSagvMXSKQ\nsFs2cn2Fi8ZmJPJXIP114lgvFuFDO+C1yTNbHap/FufvAKGryfPDuPecCF6FSXej\n+bwg4/BNz5lcHbNo2XXjLgoPg4VE6mG/SQQZQEDBk5DowwMVMvh77t9RBNrHozah\nHGtQz2hCuIX7rZQYnSlvW8T75FhI/Sd+HEfU/iyTIELXBUjypnK2bOJL7+jE7f79\nuljhXlCcP52fGHCjexNBz5gIZr82KVxsfxKuZjfioPkhmWleVNMdMWYJRXu618E6\nNtNjUVsDCuMOOMNs1qScqxOT60MeDZLX+vnC93fdd/t2hLEAWWNNMkWeX2qLCE1q\nGarop9U1mJpiBWkW5cBiqnNIbhuV2fcwFIR8mVT5f1Qcw+WxE2nEjY2h75bKv8T5\n3RBngmaX8PcyLAP2s0/4UyzAnMYfioJBh37VpUYBrdriBkRds/AMZw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFnjCCBIagAwIBAgIQCSYyO0lk42hGFRLe8aXVLDANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\nMjAeFw0yNTAxMDgwMDAwMDBaFw0zNTAxMDcyMzU5NTlaMFsxCzAJBgNVBAYTAkNO\nMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSUwIwYDVQQD\nExxUcnVzdEFzaWEgRFYgVExTIFJTQSBDQSAyMDI1MIICIjANBgkqhkiG9w0BAQEF\nAAOCAg8AMIICCgKCAgEA0fuEmuBIsN6ZZVq+gRobMorOGIilTCIfQrxNpR8FUZ9R\n/GfbiekbiIKphQXEZ7N1uBnn6tXUuZ32zl6jPkZpHzN/Bmgk1BWSIzVc0npMzrWq\n/hrbk5+KddXJdsNpeG1+Q8lc8uVMBrztnxaPb7Rh7yQCsMrcO4hgVaqLJWkVvEfW\nULtoCHQnNaj4IroG6VxQf1oArQ8bPbwpI02lieSahRa78FQuXdoGVeQcrkhtVjZs\nON98vq5fPWZX2LFv7e5J6P9IHbzvOl8yyQjv+2/IOwhNSkaXX3bI+//bqF9XW/p7\n+gsUmHiK5YsvLjmXcvDmoDEGrXMzgX31Zl2nJ+umpRbLjwP8rxYIUsKoEwEdFoto\nAid59UEBJyw/GibwXQ5xTyKD/N6C8SFkr1+myOo4oe1UB+YgvRu6qSxIABo5kYdX\nFodLP4IgoVJdeUFs1Usa6bxYEO6EgMf5lCWt9hGZszvXYZwvyZGq3ogNXM7eKyi2\n20WzJXYMmi9TYFq2Fa95aZe4wki6YhDhhOO1g0sjITGVaB73G+JOCI9yJhv6+REN\nD40ZpboUHE8JNgMVWbG1isAMVCXqiADgXtuC+tmJWPEH9cR6OuJLEpwOzPfgAbnn\n2MRu7Tsdr8jPjTPbD0FxblX1ydW3RG30vwLF5lkTTRkHG9epMgpPMdYP7nY/08MC\nAwEAAaOCAVYwggFSMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLQSKKW0\nwB2fKXFpPNkRlkp1aVDAMB8GA1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485\nMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw\ndgYIKwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy\ndC5jb20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E\naWdpQ2VydEdsb2JhbFJvb3RHMi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDov\nL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDARBgNV\nHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQELBQADggEBAJ4a3svh316GY2+Z7EYx\nmBIsOwjJSnyoEfzx2T699ctLLrvuzS79Mg3pPjxSLlUgyM8UzrFc5tgVU3dZ1sFQ\nI4RM+ysJdvIAX/7Yx1QbooVdKhkdi9X7QN7yVkjqwM3fY3WfQkRTzhIkM7mYIQbR\nr+y2Vkju61BLqh7OCRpPMiudjEpP1kEtRyGs2g0aQpEIqKBzxgitCXSayO1hoO6/\n71ts801OzYlqYW9OQQQ2GCJyFbD6XHDjdpn+bWUxTKWaMY0qedSCbHE3Kl2QEF0C\nynZ7SbC03yR+gKZQDeTXrNP1kk5Qhe7jSXgw+nhbspe0q/M1ZcNCz+sPxeOwdCcC\ngJE=\n-----END CERTIFICATE-----", +// "issuer": "cert-issuer", +// }, +// } +// err := DeployBtWaf(cfg) +// fmt.Println(err) +//} + +func TestGetBTWAFSiteList(t *testing.T) { + res, err := GetBTWafSiteList(1, 10, "xxx.cn", "1") + for _, site := range res { + sites := site.(map[string]any) + fmt.Printf("网站名: %s ID:%s \n", sites["site_name"], sites["site_id"]) + } + fmt.Println(err) +} diff --git a/backend/internal/cert/deploy/deploy.go b/backend/internal/cert/deploy/deploy.go index d5b18a9..1eadffc 100644 --- a/backend/internal/cert/deploy/deploy.go +++ b/backend/internal/cert/deploy/deploy.go @@ -17,6 +17,9 @@ func Deploy(cfg map[string]any, logger *public.Logger) error { case "btpanel-site": logger.Debug("部署到宝塔面板网站...") return DeployBtSite(cfg) + case "btwaf-site": + logger.Debug("部署到宝塔WAF面板网站...") + return DeployBtWafSite(cfg) case "tencentcloud-cdn": cfg["resource_type"] = "cdn" logger.Debug("部署到腾讯云CDN...") @@ -40,6 +43,9 @@ func Deploy(cfg map[string]any, logger *public.Logger) error { case "aliyun-oss": logger.Debug("部署到阿里云OSS...") return DeployOss(cfg) + case "localhost": + logger.Debug("部署到本地...") + return DeployLocalhost(cfg) default: return fmt.Errorf("不支持的部署: %s", providerName) } diff --git a/backend/internal/cert/deploy/ssh.go b/backend/internal/cert/deploy/ssh.go index ad4e331..9a86090 100644 --- a/backend/internal/cert/deploy/ssh.go +++ b/backend/internal/cert/deploy/ssh.go @@ -2,19 +2,22 @@ package deploy import ( "ALLinSSL/backend/internal/access" + "ALLinSSL/backend/public" "bytes" "encoding/json" "fmt" "golang.org/x/crypto/ssh" + "os" + "path/filepath" "strconv" ) type SSHConfig struct { User string Password string // 可选 - PrivateKey string // 可选 + PrivateKey string `json:"key"` // 可选 Host string - Port float64 + Port any } type RemoteFile struct { @@ -45,7 +48,18 @@ func buildAuthMethods(password, privateKey string) ([]ssh.AuthMethod, error) { } func writeMultipleFilesViaSSH(config SSHConfig, files []RemoteFile, preCmd, postCmd string) error { - addr := fmt.Sprintf("%s:%d", config.Host, int(config.Port)) + var port string + switch v := config.Port.(type) { + case float64: + port = strconv.Itoa(int(v)) + case string: + port = v + case int: + port = strconv.Itoa(v) + default: + port = "22" + } + addr := fmt.Sprintf("%s:%s", config.Host, port) authMethods, err := buildAuthMethods(config.Password, config.PrivateKey) if err != nil { @@ -164,3 +178,60 @@ func DeploySSH(cfg map[string]any) error { } return nil } + +func DeployLocalhost(cfg map[string]any) error { + cert, ok := cfg["certificate"].(map[string]any) + if !ok { + return fmt.Errorf("证书不存在") + } + // 设置证书 + keyPem, ok := cert["key"].(string) + if !ok { + return fmt.Errorf("证书错误:key") + } + certPem, ok := cert["cert"].(string) + if !ok { + return fmt.Errorf("证书错误:cert") + } + keyPath, ok := cfg["keyPath"].(string) + if !ok { + return fmt.Errorf("参数错误:keyPath") + } + certPath, ok := cfg["certPath"].(string) + if !ok { + return fmt.Errorf("参数错误:certPath") + } + beforeCmd, ok := cfg["beforeCmd"].(string) + if ok { + _, errout, err := public.ExecCommand(beforeCmd) + if err != nil { + return fmt.Errorf("前置命令执行失败: %v, %s", err, errout) + } + } + + dir := filepath.Dir(certPath) + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + panic("创建证书保存目录失败: " + err.Error()) + } + dir = filepath.Dir(keyPath) + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + panic("创建私钥保存目录失败: " + err.Error()) + } + err := os.WriteFile(certPath, []byte(certPem), 0644) + if err != nil { + return fmt.Errorf("写入证书失败: %v", err) + } + err = os.WriteFile(keyPath, []byte(keyPem), 0644) + if err != nil { + return fmt.Errorf("写入私钥失败: %v", err) + } + + afterCmd, ok := cfg["afterCmd"].(string) + if ok { + _, errout, err := public.ExecCommand(afterCmd) + if err != nil { + return fmt.Errorf("后置命令执行失败: %v, %s", err, errout) + } + } + return nil +} diff --git a/backend/internal/workflow/executor.go b/backend/internal/workflow/executor.go index 00ada84..b531195 100644 --- a/backend/internal/workflow/executor.go +++ b/backend/internal/workflow/executor.go @@ -55,13 +55,90 @@ func deploy(params map[string]any) (any, error) { logger.Info("=============部署失败=============") return nil, errors.New("证书不存在") } - err := certDeploy.Deploy(params, logger) + certificateMap, ok := params["certificate"].(map[string]any) + if !ok { + logger.Error("证书不存在") + logger.Info("=============部署失败=============") + return nil, errors.New("证书不存在") + } + certStr, ok := certificateMap["cert"].(string) + if !ok { + logger.Error("证书格式错误") + logger.Info("=============部署失败=============") + return nil, errors.New("证书格式错误") + } + nowSha256, err := public.GetSHA256(certStr) if err != nil { + logger.Error("解析证书sha256失败:" + err.Error()) + logger.Info("=============部署失败=============") + return nil, err + } + + s, err := public.NewSqlite("data/data.db", "") + if err != nil { + logger.Error("新建数据库连接失败" + err.Error()) + logger.Info("=============部署失败=============") + return nil, err + } + defer s.Close() + s.TableName = "workflow_history" + historyData, err := s.Where("id=?", []any{params["_runId"]}).Find() + if err != nil { + logger.Error("查询表workflow_history失败" + err.Error()) + logger.Info("=============部署失败=============") + return nil, err + } + workflowId := historyData["workflow_id"] + s.TableName = "workflow_deploy" + deployData, err := s.Where("workflow_id=? and id=?", []any{workflowId, params["NodeId"]}).Select() + if err != nil { + logger.Error("查询表workflow_deploy失败" + err.Error()) + logger.Info("=============部署失败=============") + return nil, err + } + + if params["skip"] != nil { + var skip int + switch v := params["skip"].(type) { + case int: + skip = v + case float64: + skip = int(v) + case string: + skip, _ = strconv.Atoi(v) + } + if skip == 1 { + if len(deployData) > 0 { + beSha256, ok := deployData[0]["cert_hash"].(string) + if !ok { + logger.Error("证书hash格式错误") + logger.Info("=============部署失败=============") + return nil, errors.New("证书hash格式错误") + } + if beSha256 == nowSha256 && deployData[0]["status"].(string) == "success" { + logger.Info("与上次部署的证书sha256相同且上次部署成功,跳过重复部署") + logger.Info("=============部署成功=============") + return nil, nil + } + } + } + } + + err = certDeploy.Deploy(params, logger) + var status string + if err != nil { + status = "fail" logger.Error(err.Error()) logger.Info("=============部署失败=============") } else { + status = "success" logger.Info("=============部署成功=============") } + if len(deployData) > 0 { + s.Where("workflow_id=? and id=?", []any{workflowId, params["NodeId"]}).Update(map[string]interface{}{"cert_hash": nowSha256, "status": status}) + } else { + s.Insert(map[string]interface{}{"cert_hash": nowSha256, "workflow_id": workflowId, "id": params["NodeId"], "status": status}) + } return nil, err } diff --git a/backend/internal/workflow/workflow.go b/backend/internal/workflow/workflow.go index 8d63876..bbc051b 100644 --- a/backend/internal/workflow/workflow.go +++ b/backend/internal/workflow/workflow.go @@ -218,6 +218,7 @@ func RunNode(node *WorkflowNode, ctx *ExecutionContext) error { } node.Config["_runId"] = ctx.RunID node.Config["logger"] = ctx.Logger + node.Config["NodeId"] = node.Id // 执行当前节点 result, err := Executors(node.Type, node.Config) diff --git a/backend/migrations/init.go b/backend/migrations/init.go index 6d448ad..8d22761 100644 --- a/backend/migrations/init.go +++ b/backend/migrations/init.go @@ -27,9 +27,9 @@ func init() { fmt.Fprintf(os.Stderr, "切换目录失败: %v\n", err) os.Exit(1) } - + os.MkdirAll("data", os.ModePerm) - + dbPath := "data/data.db" _, _ = filepath.Abs(dbPath) // fmt.Println("数据库路径:", absPath) @@ -41,6 +41,8 @@ func init() { defer db.Close() // 创建表 _, err = db.Exec(` + PRAGMA journal_mode=WAL; + create table IF NOT EXISTS _accounts ( id integer not null @@ -184,6 +186,17 @@ func init() { end_time TEXT, workflow_id TEXT not null ); + + create table workflow_deploy + ( + id TEXT, + workflow_id TEXT, + cert_hash TEXT, + status TEXT, + constraint workflow_deploy_pk + primary key (id, workflow_id) + ); + `) insertDefaultData(db, "users", "INSERT INTO users (id, username, password, salt) VALUES (1, 'xxxx', 'xxxxxxx', '&*ghs^&%dag');") insertDefaultData(db, "access_type", ` @@ -194,15 +207,15 @@ func init() { INSERT INTO access_type (name, type) VALUES ('ssh', 'host'); INSERT INTO access_type (name, type) VALUES ('btpanel', 'host'); INSERT INTO access_type (name, type) VALUES ('1panel', 'host');`) - + uuidStr := public.GenerateUUID() randomStr := public.RandomString(8) - + port, err := public.GetFreePort() if err != nil { port = 20773 } - + Isql := fmt.Sprintf( `INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ('log_path', 'logs/ALLinSSL.log', '2025-04-15 15:58', '2025-04-15 15:58', 1, null); INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ( 'workflow_log_path', 'logs/workflows/', '2025-04-15 15:58', '2025-04-15 15:58', 1, null); @@ -212,14 +225,18 @@ INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ('session_key', '%s', '2025-04-15 15:58', '2025-04-15 15:58', 1, null); INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ('secure', '/%s', '2025-04-15 15:58', '2025-04-15 15:58', 1, null); INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ('port', '%d', '2025-04-15 15:58', '2025-04-15 15:58', 1, null);`, uuidStr, uuidStr, randomStr, port) - + insertDefaultData(db, "settings", Isql) - + InsertIfNotExists(db, "access_type", map[string]any{"name": "cloudflare", "type": "host"}, []string{"name", "type"}, []any{"cloudflare", "host"}) InsertIfNotExists(db, "access_type", map[string]any{"name": "cloudflare", "type": "dns"}, []string{"name", "type"}, []any{"cloudflare", "dns"}) InsertIfNotExists(db, "access_type", map[string]any{"name": "huaweicloud", "type": "host"}, []string{"name", "type"}, []any{"huaweicloud", "host"}) InsertIfNotExists(db, "access_type", map[string]any{"name": "huaweicloud", "type": "dns"}, []string{"name", "type"}, []any{"huaweicloud", "dns"}) - + + InsertIfNotExists(db, "access_type", map[string]any{"name": "baidu", "type": "host"}, []string{"name", "type"}, []any{"baidu", "host"}) + InsertIfNotExists(db, "access_type", map[string]any{"name": "baidu", "type": "dns"}, []string{"name", "type"}, []any{"baidu", "dns"}) + + InsertIfNotExists(db, "access_type", map[string]any{"name": "btwaf", "type": "host"}, []string{"name", "type"}, []any{"btwaf", "host"}) } func insertDefaultData(db *sql.DB, table, insertSQL string) { @@ -230,7 +247,7 @@ func insertDefaultData(db *sql.DB, table, insertSQL string) { // fmt.Println("检查数据行数失败:", err) return } - + // 如果表为空,则插入默认数据 if count == 0 { // fmt.Println("表为空,插入默认数据...") @@ -264,7 +281,7 @@ func InsertIfNotExists( whereArgs = append(whereArgs, val) i++ } - + // 2. 判断是否存在 query := fmt.Sprintf("SELECT EXISTS(SELECT 1 FROM %s WHERE %s)", table, whereClause) var exists bool @@ -275,7 +292,7 @@ func InsertIfNotExists( if exists { return nil // 已存在 } - + // 3. 构建 INSERT 语句 columnList := "" placeholderList := "" @@ -288,11 +305,11 @@ func InsertIfNotExists( placeholderList += "?" } insertSQL := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", table, columnList, placeholderList) - + _, err = db.Exec(insertSQL, insertValues...) if err != nil { return fmt.Errorf("insert failed: %w", err) } - + return nil } diff --git a/backend/public/utils.go b/backend/public/utils.go index 811b86e..c4c8d98 100644 --- a/backend/public/utils.go +++ b/backend/public/utils.go @@ -1,6 +1,7 @@ package public import ( + "bytes" "crypto/rand" "fmt" "github.com/google/uuid" @@ -8,6 +9,8 @@ import ( "math/big" "net" "net/http" + "os/exec" + "runtime" "strings" ) @@ -200,3 +203,23 @@ func ContainsAllIgnoreBRepeats(a, b []string) bool { } return true } + +// ExecCommand 执行系统命令,并返回 stdout、stderr 和错误 +func ExecCommand(command string) (string, string, error) { + var cmd *exec.Cmd + + // 根据操作系统选择解释器 + if runtime.GOOS == "windows" { + cmd = exec.Command("cmd", "/C", command) + } else { + cmd = exec.Command("bash", "-c", command) + } + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + + return stdout.String(), stderr.String(), err +}