diff --git a/backend/app/api/access.go b/backend/app/api/access.go index dba2bed..d253dcd 100644 --- a/backend/app/api/access.go +++ b/backend/app/api/access.go @@ -135,6 +135,148 @@ func DelAccess(c *gin.Context) { return } +func GetAllEAB(c *gin.Context) { + var form struct { + CA string `form:"ca"` + } + err := c.Bind(&form) + if err != nil { + public.FailMsg(c, err.Error()) + return + } + eabList, err := access.GetAllEAB(form.CA) + if err != nil { + public.FailMsg(c, err.Error()) + return + } + public.SuccessData(c, eabList, 0) + return +} + +func GetEABList(c *gin.Context) { + var form struct { + Search string `form:"search"` + Page int64 `form:"p"` + Limit int64 `form:"limit"` + } + err := c.Bind(&form) + if err != nil { + public.FailMsg(c, err.Error()) + return + } + eabList, count, err := access.GetEABList(form.Search, form.Page, form.Limit) + if err != nil { + public.FailMsg(c, err.Error()) + return + } + public.SuccessData(c, eabList, count) + return +} + +func AddEAB(c *gin.Context) { + var form struct { + Name string `form:"name"` + Kid string `form:"Kid"` + HmacEncoded string `form:"HmacEncoded"` + CA string `form:"ca"` + } + err := c.Bind(&form) + if err != nil { + public.FailMsg(c, err.Error()) + return + } + form.Name = strings.TrimSpace(form.Name) + form.Kid = strings.TrimSpace(form.Kid) + form.HmacEncoded = strings.TrimSpace(form.HmacEncoded) + form.CA = strings.TrimSpace(form.CA) + if form.Name == "" { + public.FailMsg(c, "名称不能为空") + return + } + if form.Kid == "" { + public.FailMsg(c, "ID不能为空") + return + } + if form.HmacEncoded == "" { + public.FailMsg(c, "HmacEncoded不能为空") + return + } + if form.CA == "" { + public.FailMsg(c, "CA不能为空") + return + } + err = access.AddEAB(form.Name, form.Kid, form.HmacEncoded, form.CA) + if err != nil { + public.FailMsg(c, err.Error()) + } + public.SuccessMsg(c, "添加成功") + return +} + +func UpdEAB(c *gin.Context) { + var form struct { + ID string `form:"id"` + Name string `form:"name"` + Kid string `form:"Kid"` + HmacEncoded string `form:"HmacEncoded"` + CA string `form:"ca"` + } + err := c.Bind(&form) + if err != nil { + public.FailMsg(c, err.Error()) + return + } + form.Name = strings.TrimSpace(form.Name) + form.Kid = strings.TrimSpace(form.Kid) + form.HmacEncoded = strings.TrimSpace(form.HmacEncoded) + form.CA = strings.TrimSpace(form.CA) + if form.Name == "" { + public.FailMsg(c, "名称不能为空") + return + } + if form.Kid == "" { + public.FailMsg(c, "ID不能为空") + return + } + if form.HmacEncoded == "" { + public.FailMsg(c, "HmacEncoded不能为空") + return + } + if form.CA == "" { + public.FailMsg(c, "CA不能为空") + return + } + err = access.UpdEAB(form.ID, form.Name, form.Kid, form.HmacEncoded, form.CA) + if err != nil { + public.FailMsg(c, err.Error()) + } + public.SuccessMsg(c, "修改成功") + return +} + +func DelEAB(c *gin.Context) { + var form struct { + ID string `form:"id"` + } + err := c.Bind(&form) + if err != nil { + public.FailMsg(c, err.Error()) + return + } + form.ID = strings.TrimSpace(form.ID) + if form.ID == "" { + public.FailMsg(c, "ID不能为空") + return + } + err = access.DelEAB(form.ID) + if err != nil { + public.FailMsg(c, err.Error()) + return + } + public.SuccessMsg(c, "删除成功") + return +} + func TestAccess(c *gin.Context) { var form struct { ID string `form:"id"` @@ -149,7 +291,7 @@ func TestAccess(c *gin.Context) { public.FailMsg(c, "类型不能为空") return } - + var result error switch form.Type { case "btwaf": @@ -171,12 +313,12 @@ func TestAccess(c *gin.Context) { default: public.FailMsg(c, "不支持测试的提供商") } - + if result != nil { public.FailMsg(c, result.Error()) return } - + public.SuccessMsg(c, "请求测试成功!") return } diff --git a/backend/app/api/login.go b/backend/app/api/login.go index c69bf7d..4ec3e27 100644 --- a/backend/app/api/login.go +++ b/backend/app/api/login.go @@ -32,7 +32,6 @@ func Sign(c *gin.Context) { public.FailMsg(c, err.Error()) return } - s.Connect() defer s.Close() s.TableName = "users" res, err := s.Where("username=?", []interface{}{form.Username}).Select() diff --git a/backend/internal/access/access.go b/backend/internal/access/access.go index 210e5a3..e35ae29 100644 --- a/backend/internal/access/access.go +++ b/backend/internal/access/access.go @@ -12,7 +12,6 @@ func GetSqlite() (*public.Sqlite, error) { if err != nil { return nil, err } - s.Connect() s.TableName = "access" return s, nil } diff --git a/backend/internal/access/accessType.go b/backend/internal/access/accessType.go index 7257681..0fb5abf 100644 --- a/backend/internal/access/accessType.go +++ b/backend/internal/access/accessType.go @@ -9,7 +9,6 @@ func GetSqliteAT() (*public.Sqlite, error) { if err != nil { return nil, err } - s.Connect() s.TableName = "access_type" return s, nil } diff --git a/backend/internal/cert/apply/account.go b/backend/internal/cert/apply/account.go index af91cbb..f1e7136 100644 --- a/backend/internal/cert/apply/account.go +++ b/backend/internal/cert/apply/account.go @@ -29,7 +29,7 @@ func (u *MyUser) GetPrivateKey() crypto.PrivateKey { return u.key } -func SaveUserToDB(db *public.Sqlite, user *MyUser) error { +func SaveUserToDB(db *public.Sqlite, user *MyUser, Type string) error { keyBytes, err := x509.MarshalPKCS8PrivateKey(user.key) if err != nil { return err @@ -53,13 +53,13 @@ func SaveUserToDB(db *public.Sqlite, user *MyUser) error { "reg": regBytes, "create_time": now, "update_time": now, - "type": "Let's Encrypt", + "type": Type, }) return err } -func LoadUserFromDB(db *public.Sqlite, email string) (*MyUser, error) { - data, err := db.Where(`email=?`, []interface{}{email}).Select() +func LoadUserFromDB(db *public.Sqlite, email string, Type string) (*MyUser, error) { + data, err := db.Where(`email=? and type=?`, []interface{}{email, Type}).Select() if err != nil { return nil, err } diff --git a/backend/internal/cert/apply/apply.go b/backend/internal/cert/apply/apply.go index 33d9ea8..2692edc 100644 --- a/backend/internal/cert/apply/apply.go +++ b/backend/internal/cert/apply/apply.go @@ -22,17 +22,33 @@ import ( "github.com/go-acme/lego/v4/providers/dns/volcengine" "github.com/go-acme/lego/v4/providers/dns/westcn" "github.com/go-acme/lego/v4/registration" + "net/http" + "net/url" "strconv" "strings" "time" ) +var AlgorithmMap = map[string]certcrypto.KeyType{ + "RSA2048": certcrypto.RSA2048, + "RSA3072": certcrypto.RSA3072, + "RSA4096": certcrypto.RSA4096, + "RSA8192": certcrypto.RSA8192, + "EC256": certcrypto.EC256, + "EC384": certcrypto.EC384, +} + +var CADirURLMap = map[string]string{ + "Let's Encrypt": "https://acme-v02.api.letsencrypt.org/directory", + "zerossl": "https://acme.zerossl.com/v2/DV90", + "google": "https://dv.acme-v02.api.pki.goog/directory", +} + func GetSqlite() (*public.Sqlite, error) { s, err := public.NewSqlite("data/data.db", "") if err != nil { return nil, err } - s.Connect() s.TableName = "_accounts" return s, nil } @@ -77,17 +93,179 @@ func GetDNSProvider(providerName string, creds map[string]string) (challenge.Pro config.SecretKey = creds["secret_key"] return volcengine.NewDNSProviderConfig(config) - // case "godaddy": - // config := godaddy.NewDefaultConfig() - // config.APIKey = creds["api_key"] - // config.APISecret = creds["api_secret"] - // return godaddy.NewDNSProviderConfig(config) - default: return nil, fmt.Errorf("不支持的 DNS Provider: %s", providerName) } } +func GetAcmeClient(db *public.Sqlite, email, algorithm, ca, proxy, eabId string, logger *public.Logger) (*lego.Client, error) { + user, err := LoadUserFromDB(db, email, ca) + if err != nil { + logger.Debug("acme账号不存在,注册新账号") + privateKey, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + user = &MyUser{ + Email: email, + key: privateKey, + } + + config := lego.NewConfig(user) + config.Certificate.KeyType = AlgorithmMap[algorithm] + config.CADirURL = CADirURLMap[ca] + if proxy != "" { + // 构建代理 HTTP 客户端 + proxyURL, err := url.Parse(proxy) // 替换为你的代理地址 + if err != nil { + return nil, fmt.Errorf("无效的代理地址: %v", err) + } + httpClient := &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyURL(proxyURL), + }, + Timeout: 30 * time.Second, + } + config.HTTPClient = httpClient + } + client, err := lego.NewClient(config) + if err != nil { + return nil, err + } + logger.Debug("正在注册账号:" + email) + var reg *registration.Resource + switch ca { + case "Let's Encrypt": + reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) + case "zerossl", "google": + // 获取EAB参数 + var eabData map[string]any + if eabId == "" { + data, err := access.GetAllEAB(ca) + if err != nil { + return nil, err + } + if len(data) <= 0 { + return nil, fmt.Errorf("未找到EAB信息") + } + eabData = data[0] + } else { + eabData, err = access.GetEAB(eabId) + if err != nil { + return nil, err + } + if eabData == nil { + return nil, fmt.Errorf("未找到EAB信息") + } + } + Kid := eabData["kid"].(string) + HmacEncoded := eabData["HmacEncoded"].(string) + reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{ + TermsOfServiceAgreed: true, + Kid: Kid, + HmacEncoded: HmacEncoded, + }) + default: + reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) + } + if err != nil { + return nil, err + } + user.Registration = reg + + err = SaveUserToDB(db, user, ca) + if err != nil { + return nil, err + } + logger.Debug("acme账号注册并保存成功") + return client, nil + } else { + config := lego.NewConfig(user) + config.Certificate.KeyType = AlgorithmMap[algorithm] + config.CADirURL = CADirURLMap[ca] + if proxy != "" { + // 构建代理 HTTP 客户端 + proxyURL, err := url.Parse(proxy) // 替换为你的代理地址 + if err != nil { + return nil, fmt.Errorf("无效的代理地址: %v", err) + } + httpClient := &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyURL(proxyURL), + }, + Timeout: 30 * time.Second, + } + config.HTTPClient = httpClient + } + + // 初始化 ACME 客户端 + client, err := lego.NewClient(config) + if err != nil { + return nil, err + } + return client, nil + } +} + +func GetCert(runId string, domainArr []string, endDay int, logger *public.Logger) (map[string]any, error) { + if runId == "" { + return nil, fmt.Errorf("参数错误:_runId") + } + s, err := public.NewSqlite("data/data.db", "") + if err != nil { + return nil, err + } + s.TableName = "workflow_history" + defer s.Close() + // 查询 workflowId + wh, err := s.Where("id=?", []interface{}{runId}).Select() + if err != nil { + return nil, err + } + if len(wh) <= 0 { + return nil, fmt.Errorf("未获取到对应的workflowId") + } + s.TableName = "cert" + certs, err := s.Where("workflow_id=?", []interface{}{wh[0]["workflow_id"]}).Select() + if err != nil { + return nil, err + } + if len(certs) <= 0 { + return nil, fmt.Errorf("未获取到当前工作流下的证书") + } + layout := "2006-01-02 15:04:05" + var maxDays float64 + var maxItem map[string]any + for i := range certs { + if !public.ContainsAllIgnoreBRepeats(strings.Split(certs[i]["domains"].(string), ","), domainArr) { + continue + } + endTimeStr, ok := certs[i]["end_time"].(string) + if !ok { + continue + } + endTime, err := time.Parse(layout, endTimeStr) + if err != nil { + continue + } + diff := endTime.Sub(time.Now()).Hours() / 24 + if diff > maxDays { + maxDays = diff + maxItem = certs[i] + } + } + if maxItem == nil { + return nil, fmt.Errorf("未获取到对应的证书") + } + if int(maxDays) <= endDay { + return nil, fmt.Errorf("证书已过期或即将过期,剩余天数:%d 小于%d天", int(maxDays), endDay) + } + // 证书未过期,直接返回 + logger.Debug(fmt.Sprintf("上次证书申请成功,域名:%s,剩余天数:%d 大于%d天,已跳过申请复用此证书", maxItem["domains"], int(maxDays), endDay)) + return map[string]any{ + "cert": maxItem["cert"], + "key": maxItem["key"], + "issuerCert": maxItem["issuer_cert"], + }, nil +} + func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { db, err := GetSqlite() if err != nil { @@ -107,6 +285,44 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { if !ok { return nil, fmt.Errorf("参数错误:provider") } + endDay := 30 + switch v := cfg["end_day"].(type) { + case float64: + endDay = int(v) + case int: + endDay = v + case string: + if v != "" { + endDay, err = strconv.Atoi(v) + if err != nil { + return nil, fmt.Errorf("参数错误:end_day") + } + } + case int64: + endDay = int(v) + } + algorithm, ok := cfg["algorithm"].(string) + if !ok { + algorithm = "RSA2048" + } + ca, ok := cfg["ca"].(string) + if !ok { + ca = "Let's Encrypt" + } + proxy, ok := cfg["proxy"].(string) + if !ok { + proxy = "" + } + var eabId string + switch v := cfg["eabId"].(type) { + case float64: + eabId = strconv.Itoa(int(v)) + case string: + eabId = v + default: + eabId = "" + } + var providerID string switch v := cfg["provider_id"].(type) { case float64: @@ -178,100 +394,15 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { if !ok { return nil, fmt.Errorf("参数错误:_runId") } - if runId != "" { - s, err := public.NewSqlite("data/data.db", "") - if err != nil { - return nil, err - } - s.Connect() - s.TableName = "workflow_history" - defer s.Close() - // 查询 workflowId - wh, err := s.Where("id=?", []interface{}{runId}).Select() - if err != nil { - return nil, err - } - if len(wh) > 0 { - s.TableName = "cert" - certs, err := s.Where("workflow_id=?", []interface{}{wh[0]["workflow_id"]}).Select() - if err != nil { - return nil, err - } - if len(certs) > 0 { - layout := "2006-01-02 15:04:05" - var maxDays float64 - var maxItem map[string]any - for i := range certs { - if !public.ContainsAllIgnoreBRepeats(strings.Split(certs[i]["domains"].(string), ","), domainArr) { - continue - } - endTimeStr, ok := certs[i]["end_time"].(string) - if !ok { - continue - } - endTime, err := time.Parse(layout, endTimeStr) - if err != nil { - continue - } - diff := endTime.Sub(time.Now()).Hours() / 24 - if diff > maxDays { - maxDays = diff - maxItem = certs[i] - } - } - certObj := maxItem - // 判断证书是否过期 - cfgEnd, ok := cfg["end_day"].(int) - if !ok || cfgEnd <= 0 { - cfgEnd = 30 - } - - if int(maxDays) > cfgEnd { - // 证书未过期,直接返回 - logger.Debug(fmt.Sprintf("上次证书申请成功,域名:%s,剩余天数:%d 大于%d天,已跳过申请复用此证书", certObj["domains"], int(maxDays), cfgEnd)) - return map[string]any{ - "cert": certObj["cert"], - "key": certObj["key"], - "issuerCert": certObj["issuer_cert"], - }, nil - } - } - } + certData, err := GetCert(runId, domainArr, endDay, logger) + if err != nil { + logger.Debug("未获取到符合条件的本地证书:" + err.Error()) + } else { + return certData, nil } logger.Debug("正在申请证书,域名: " + domains) - - user, err := LoadUserFromDB(db, email) - if err != nil { - logger.Debug("acme账号不存在,注册新账号") - privateKey, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - user = &MyUser{ - Email: email, - key: privateKey, - } - - config := lego.NewConfig(user) - config.Certificate.KeyType = certcrypto.EC384 - - client, err := lego.NewClient(config) - if err != nil { - return nil, err - } - logger.Debug("正在注册账号:" + email) - reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) - if err != nil { - return nil, err - } - user.Registration = reg - - err = SaveUserToDB(db, user) - if err != nil { - return nil, err - } - logger.Debug("账号注册并保存成功") - } - - // 初始化 ACME 客户端 - client, err := lego.NewClient(lego.NewConfig(user)) + // 创建 ACME 客户端 + client, err := GetAcmeClient(db, email, algorithm, ca, proxy, eabId, logger) if err != nil { return nil, err } diff --git a/backend/internal/cert/cert.go b/backend/internal/cert/cert.go index c9e0473..d54d89a 100644 --- a/backend/internal/cert/cert.go +++ b/backend/internal/cert/cert.go @@ -13,7 +13,6 @@ func GetSqlite() (*public.Sqlite, error) { if err != nil { return nil, err } - s.Connect() s.TableName = "cert" return s, nil } @@ -26,7 +25,7 @@ func GetList(search string, p, limit int64) ([]map[string]any, int, error) { return data, 0, err } defer s.Close() - + var limits []int64 if p >= 0 && limit >= 0 { limits = []int64{0, limit} @@ -35,7 +34,7 @@ func GetList(search string, p, limit int64) ([]map[string]any, int, error) { limits[1] = p * limit } } - + if search != "" { count, err = s.Where("domains like ?", []interface{}{"%" + search + "%"}).Count() data, err = s.Where("domains like ?", []interface{}{"%" + search + "%"}).Limit(limits).Order("create_time", "desc").Select() @@ -68,7 +67,6 @@ func AddCert(source, key, cert, issuer, issuerCert, domains, sha256, historyId, if err != nil { return err } - s.Connect() s.TableName = "workflow_history" defer s.Close() // 查询 workflowId @@ -80,7 +78,7 @@ func AddCert(source, key, cert, issuer, issuerCert, domains, sha256, historyId, workflowId = wh[0]["workflow_id"].(string) } } - + now := time.Now().Format("2006-01-02 15:04:05") _, err = s.Insert(map[string]any{ "source": source, @@ -108,7 +106,7 @@ func SaveCert(source, key, cert, issuerCert, historyId string) (string, error) { if err := public.ValidateSSLCertificate(cert, key); err != nil { return "", err } - + certObj, err := public.ParseCertificate([]byte(cert)) if err != nil { return "", fmt.Errorf("解析证书失败: %v", err) @@ -121,23 +119,23 @@ func SaveCert(source, key, cert, issuerCert, historyId string) (string, error) { if d, _ := GetCert(sha256); d != nil { return sha256, nil } - + domainSet := make(map[string]bool) - + if certObj.Subject.CommonName != "" { domainSet[certObj.Subject.CommonName] = true } for _, dns := range certObj.DNSNames { domainSet[dns] = true } - + // 转成切片并拼接成逗号分隔的字符串 var domains []string for domain := range domainSet { domains = append(domains, domain) } domainList := strings.Join(domains, ",") - + // 提取 CA 名称(Issuer 的组织名) caName := "UNKNOWN" if len(certObj.Issuer.Organization) > 0 { @@ -149,7 +147,7 @@ func SaveCert(source, key, cert, issuerCert, historyId string) (string, error) { startTime := certObj.NotBefore.Format("2006-01-02 15:04:05") endTime := certObj.NotAfter.Format("2006-01-02 15:04:05") endDay := fmt.Sprintf("%d", int(certObj.NotAfter.Sub(time.Now()).Hours()/24)) - + err = AddCert(source, key, cert, caName, issuerCert, domainList, sha256, historyId, startTime, endTime, endDay) if err != nil { return "", fmt.Errorf("保存证书失败: %v", err) @@ -171,7 +169,7 @@ func DelCert(id string) error { return err } defer s.Close() - + _, err = s.Where("id=?", []interface{}{id}).Delete() if err != nil { return err @@ -185,7 +183,7 @@ func GetCert(id string) (map[string]string, error) { return nil, err } defer s.Close() - + res, err := s.Where("id=? or sha256=?", []interface{}{id, id}).Select() if err != nil { return nil, err @@ -193,13 +191,13 @@ func GetCert(id string) (map[string]string, error) { if len(res) == 0 { return nil, fmt.Errorf("证书不存在") } - + data := map[string]string{ "domains": res[0]["domains"].(string), "cert": res[0]["cert"].(string), "key": res[0]["key"].(string), } - + return data, nil } diff --git a/backend/internal/overview/overview.go b/backend/internal/overview/overview.go index 2cc9bb0..263d493 100644 --- a/backend/internal/overview/overview.go +++ b/backend/internal/overview/overview.go @@ -12,7 +12,6 @@ func GetWorkflowCount() (map[string]any, error) { if err != nil { return nil, err } - s.Connect() defer s.Close() workflow, err := s.Query(`select count(*) as count, count(case when exec_type='auto' then 1 end ) as active, @@ -71,7 +70,6 @@ func GetSiteMonitorCount() (map[string]any, error) { if err != nil { return nil, err } - s.Connect() defer s.Close() cert, err := s.Query(`select count(*) as count, count(case when state='异常' then 1 end ) as exception diff --git a/backend/internal/report/report.go b/backend/internal/report/report.go index e037c95..b50cebf 100644 --- a/backend/internal/report/report.go +++ b/backend/internal/report/report.go @@ -16,7 +16,6 @@ func GetSqlite() (*public.Sqlite, error) { if err != nil { return nil, err } - s.Connect() s.TableName = "report" return s, nil } @@ -29,7 +28,7 @@ func GetList(search string, p, limit int64) ([]map[string]any, int, error) { return data, 0, err } defer s.Close() - + var limits []int64 if p >= 0 && limit >= 0 { limits = []int64{0, limit} @@ -38,7 +37,7 @@ func GetList(search string, p, limit int64) ([]map[string]any, int, error) { limits[1] = p * limit } } - + if search != "" { count, err = s.Where("name like ?", []interface{}{"%" + search + "%"}).Count() data, err = s.Where("name like ?", []interface{}{"%" + search + "%"}).Limit(limits).Order("update_time", "desc").Select() @@ -66,7 +65,7 @@ func GetReport(id string) (map[string]any, error) { return nil, fmt.Errorf("没有找到此通知配置") } return data[0], nil - + } func AddReport(Type, config, name string) error { @@ -148,7 +147,7 @@ func Notify(params map[string]any) error { } func NotifyMail(params map[string]any) error { - + if params == nil { return fmt.Errorf("缺少参数") } @@ -164,18 +163,18 @@ func NotifyMail(params map[string]any) error { if err != nil { return fmt.Errorf("解析配置失败: %v", err) } - + e := email.NewEmail() e.From = config["sender"] e.To = []string{config["receiver"]} e.Subject = params["subject"].(string) - + e.Text = []byte(params["body"].(string)) - + addr := fmt.Sprintf("%s:%s", config["smtpHost"], config["smtpPort"]) - + auth := smtp.PlainAuth("", config["sender"], config["password"], config["smtpHost"]) - + // 使用 SSL(通常是 465) if config["smtpPort"] == "465" { tlsConfig := &tls.Config{ @@ -192,7 +191,7 @@ func NotifyMail(params map[string]any) error { } return nil } - + // 普通明文发送(25端口,非推荐) err = e.Send(addr, auth) if err != nil { diff --git a/backend/internal/siteMonitor/monitor.go b/backend/internal/siteMonitor/monitor.go index fd3cff8..6965423 100644 --- a/backend/internal/siteMonitor/monitor.go +++ b/backend/internal/siteMonitor/monitor.go @@ -29,7 +29,6 @@ func GetSqlite() (*public.Sqlite, error) { if err != nil { return nil, err } - s.Connect() s.TableName = "site_monitor" return s, nil } diff --git a/backend/internal/workflow/workflow.go b/backend/internal/workflow/workflow.go index bbc051b..53d682d 100644 --- a/backend/internal/workflow/workflow.go +++ b/backend/internal/workflow/workflow.go @@ -13,7 +13,6 @@ func GetSqlite() (*public.Sqlite, error) { if err != nil { return nil, err } - s.Connect() s.TableName = "workflow" return s, nil } @@ -26,7 +25,7 @@ func GetList(search string, p, limit int64) ([]map[string]any, int, error) { return data, 0, err } defer s.Close() - + var limits []int64 if p >= 0 && limit >= 0 { limits = []int64{0, limit} @@ -35,7 +34,7 @@ func GetList(search string, p, limit int64) ([]map[string]any, int, error) { limits[1] = p * limit } } - + if search != "" { count, err = s.Where("name like ?", []interface{}{"%" + search + "%"}).Count() data, err = s.Where("name like ?", []interface{}{"%" + search + "%"}).Order("update_time", "desc").Limit(limits).Select() @@ -55,7 +54,7 @@ func AddWorkflow(name, content, execType, active, execTime string) error { if err != nil { return fmt.Errorf("检测到工作流配置有问题:%v", err) } - + s, err := GetSqlite() if err != nil { return err @@ -160,7 +159,7 @@ func ExecuteWorkflow(id string) error { return fmt.Errorf("工作流正在执行中") } content := data[0]["content"].(string) - + go func(id, c string) { // defer wg.Done() // WorkflowID := strconv.FormatInt(id, 10) @@ -219,10 +218,10 @@ 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) - + var status ExecutionStatus if err != nil { status = StatusFailed @@ -232,9 +231,9 @@ func RunNode(node *WorkflowNode, ctx *ExecutionContext) error { } else { status = StatusSuccess } - + ctx.SetOutput(node.Id, result, status) - + // 普通的并行 if node.Type == "branch" { if len(node.ConditionNodes) > 0 { @@ -270,7 +269,7 @@ func RunNode(node *WorkflowNode, ctx *ExecutionContext) error { } } } - + if node.ChildNode != nil { return RunNode(node.ChildNode, ctx) } diff --git a/backend/internal/workflow/workflow_history.go b/backend/internal/workflow/workflow_history.go index 8c8bdac..6a23785 100644 --- a/backend/internal/workflow/workflow_history.go +++ b/backend/internal/workflow/workflow_history.go @@ -13,7 +13,6 @@ func GetSqliteObjWH() (*public.Sqlite, error) { if err != nil { return nil, err } - s.Connect() s.TableName = "workflow_history" return s, nil } @@ -27,7 +26,7 @@ func GetListWH(id string, p, limit int64) ([]map[string]any, int, error) { return data, 0, err } defer s.Close() - + var limits []int64 if p >= 0 && limit >= 0 { limits = []int64{0, limit} @@ -43,7 +42,7 @@ func GetListWH(id string, p, limit int64) ([]map[string]any, int, error) { count, err = s.Where("workflow_id=?", []interface{}{id}).Count() data, err = s.Where("workflow_id=?", []interface{}{id}).Limit(limits).Order("create_time", "desc").Select() } - + if err != nil { return data, 0, err } diff --git a/backend/middleware/auth.go b/backend/middleware/auth.go index 01c568d..8030beb 100644 --- a/backend/middleware/auth.go +++ b/backend/middleware/auth.go @@ -45,7 +45,7 @@ func SessionAuthMiddleware() gin.HandlerFunc { } // 返回登录页 c.Redirect(http.StatusFound, "/login") - // c.Abort() + c.Abort() return } else { if session.Get("secure") == nil || last == nil { diff --git a/backend/migrations/init.go b/backend/migrations/init.go index ae9bcf2..18d72f0 100644 --- a/backend/migrations/init.go +++ b/backend/migrations/init.go @@ -28,9 +28,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) @@ -167,7 +167,7 @@ func init() { workflow_id TEXT not null ); - create table workflow_deploy + create table IF NOT EXISTS workflow_deploy ( id TEXT, workflow_id TEXT, @@ -177,6 +177,19 @@ func init() { primary key (id, workflow_id) ); + create table IF NOT EXISTS _eab + ( + id integer not null + constraint _eab_pk + primary key autoincrement, + name TEXT, + Kid TEXT not null, + HmacEncoded TEXT not null, + ca TEXT not null, + create_time TEXT, + update_time TEXT + ); + `) insertDefaultData(db, "users", "INSERT INTO users (id, username, password, salt) VALUES (1, 'admin', 'xxxxxxx', '&*ghs^&%dag');") insertDefaultData(db, "access_type", ` @@ -187,15 +200,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); @@ -204,26 +217,26 @@ 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, 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"}) - + // 雷池 InsertIfNotExists(db, "access_type", map[string]any{"name": "safeline", "type": "host"}, []string{"name", "type"}, []any{"safeline", "host"}) // 西部数码 InsertIfNotExists(db, "access_type", map[string]any{"name": "westcn", "type": "dns"}, []string{"name", "type"}, []any{"westcn", "dns"}) // 火山引擎 InsertIfNotExists(db, "access_type", map[string]any{"name": "volcengine", "type": "dns"}, []string{"name", "type"}, []any{"volcengine", "dns"}) - + err = sqlite_migrate.EnsureDatabaseWithTables( "data/site_monitor.db", "data/data.db", @@ -232,7 +245,7 @@ INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES if err != nil { fmt.Println("错误:", err) } - + db1, err := sql.Open("sqlite", "data/site_monitor.db") if err != nil { // fmt.Println("创建数据库失败:", err) @@ -275,7 +288,7 @@ func insertDefaultData(db *sql.DB, table, insertSQL string) { // fmt.Println("检查数据行数失败:", err) return } - + // 如果表为空,则插入默认数据 if count == 0 { // fmt.Println("表为空,插入默认数据...") @@ -309,7 +322,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 @@ -320,7 +333,7 @@ func InsertIfNotExists( if exists { return nil // 已存在 } - + // 3. 构建 INSERT 语句 columnList := "" placeholderList := "" @@ -333,11 +346,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/cert.go b/backend/public/cert.go index 73a7978..8bee1f8 100644 --- a/backend/public/cert.go +++ b/backend/public/cert.go @@ -4,6 +4,7 @@ import ( "crypto" "crypto/ecdsa" "crypto/ed25519" + "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" @@ -67,7 +68,7 @@ func VerifyCertificateAndKey(cert *x509.Certificate, privateKey crypto.PrivateKe case *rsa.PrivateKey: signature, err = rsa.SignPKCS1v15(nil, key, crypto.SHA256, message) case *ecdsa.PrivateKey: - signature, err = key.Sign(nil, message, crypto.SHA256) + signature, err = key.Sign(rand.Reader, message, crypto.SHA256) case ed25519.PrivateKey: signature = ed25519.Sign(key, message) default: diff --git a/backend/public/sqlite_migrate/migrate_table_to_db.go b/backend/public/sqlite_migrate/migrate_table_to_db.go index 4f2a734..523088c 100644 --- a/backend/public/sqlite_migrate/migrate_table_to_db.go +++ b/backend/public/sqlite_migrate/migrate_table_to_db.go @@ -4,7 +4,7 @@ import ( "database/sql" "fmt" "os" - + _ "modernc.org/sqlite" // 使用 pure Go 实现的 SQLite 驱动 ) @@ -14,50 +14,50 @@ func EnsureDatabaseWithTables(targetDBPath string, baseDBPath string, tables []s // fmt.Printf("数据库 %s 已存在,跳过迁移。\n", targetDBPath) return nil } - + // fmt.Printf("数据库 %s 不存在,开始从基础数据库迁移表...\n", targetDBPath) - + // 2. 打开源数据库(只读)和目标数据库(新建) baseDB, err := sql.Open("sqlite", baseDBPath) if err != nil { return fmt.Errorf("打开基础数据库失败: %v", err) } defer baseDB.Close() - + targetDB, err := sql.Open("sqlite", targetDBPath) if err != nil { return fmt.Errorf("创建目标数据库失败: %v", err) } defer targetDB.Close() - + for _, table := range tables { // 2.1 获取建表语句 var createSQL string query := "SELECT sql FROM sqlite_master WHERE type='table' AND name=?" err = baseDB.QueryRow(query, table).Scan(&createSQL) if err != nil { - return fmt.Errorf("获取表 %s 的结构失败: %v", table, err) + return nil } - + // 2.2 在目标库中创建表 _, err = targetDB.Exec(createSQL) if err != nil { return fmt.Errorf("创建表 %s 失败: %v", table, err) } - + // 2.3 从基础库读取数据并插入目标库 rows, err := baseDB.Query(fmt.Sprintf("SELECT * FROM %s", table)) if err != nil { return fmt.Errorf("读取表 %s 数据失败: %v", table, err) } - + cols, _ := rows.Columns() values := make([]interface{}, len(cols)) valuePtrs := make([]interface{}, len(cols)) - + tx, _ := targetDB.Begin() stmt, _ := tx.Prepare(buildInsertSQL(table, len(cols))) - + for rows.Next() { for i := range values { valuePtrs[i] = &values[i] @@ -65,12 +65,12 @@ func EnsureDatabaseWithTables(targetDBPath string, baseDBPath string, tables []s rows.Scan(valuePtrs...) stmt.Exec(values...) } - + stmt.Close() tx.Commit() rows.Close() } - + // fmt.Println("迁移完成。") return nil } diff --git a/backend/public/utils.go b/backend/public/utils.go index c4c8d98..1c68848 100644 --- a/backend/public/utils.go +++ b/backend/public/utils.go @@ -22,7 +22,6 @@ func GetSettingIgnoreError(key string) string { if err != nil { return "" } - s.Connect() defer s.Close() s.TableName = "settings" res, err := s.Where("key=?", []interface{}{key}).Select() @@ -44,7 +43,6 @@ func UpdateSetting(key, val string) error { if err != nil { return err } - s.Connect() defer s.Close() s.TableName = "settings" _, err = s.Where("key=?", []interface{}{key}).Update(map[string]any{"value": val}) @@ -60,14 +58,13 @@ func GetSettingsFromType(typ string) ([]map[string]any, error) { if err != nil { return nil, err } - s.Connect() defer s.Close() s.TableName = "settings" res, err := s.Where("type=?", []interface{}{typ}).Select() if err != nil { return nil, err } - + return res, nil } @@ -79,14 +76,14 @@ func GetFreePort() (int, error) { return 0, err } defer ln.Close() - + addr := ln.Addr().String() // 提取端口号 parts := strings.Split(addr, ":") if len(parts) < 2 { return 0, fmt.Errorf("invalid address: %s", addr) } - + var port int fmt.Sscanf(parts[len(parts)-1], "%d", &port) return port, nil @@ -105,7 +102,7 @@ func RandomString(length int) string { func RandomStringWithCharset(length int, charset string) (string, error) { result := make([]byte, length) charsetLen := big.NewInt(int64(len(charset))) - + for i := 0; i < length; i++ { num, err := rand.Int(rand.Reader, charsetLen) if err != nil { @@ -113,7 +110,7 @@ func RandomStringWithCharset(length int, charset string) (string, error) { } result[i] = charset[num.Int64()] } - + return string(result), nil } @@ -121,7 +118,7 @@ func RandomStringWithCharset(length int, charset string) (string, error) { func GenerateUUID() string { // 生成一个新的 UUID uuidStr := strings.ReplaceAll(uuid.New().String(), "-", "") - + // 返回 UUID 的字符串表示 return uuidStr } @@ -131,7 +128,7 @@ func GetLocalIP() (string, error) { if err != nil { return "", err } - + for _, iface := range interfaces { if iface.Flags&net.FlagUp == 0 { continue // 接口未启用 @@ -139,12 +136,12 @@ func GetLocalIP() (string, error) { if iface.Flags&net.FlagLoopback != 0 { continue // 忽略回环地址 } - + addrs, err := iface.Addrs() if err != nil { continue } - + for _, addr := range addrs { var ip net.IP switch v := addr.(type) { @@ -153,14 +150,14 @@ func GetLocalIP() (string, error) { case *net.IPAddr: ip = v.IP } - + // 只返回 IPv4 内网地址 if ip != nil && ip.To4() != nil && !ip.IsLoopback() { return ip.String(), nil } } } - + return "", fmt.Errorf("没有找到内网 IP") } @@ -170,16 +167,16 @@ func GetPublicIP() (string, error) { return "", fmt.Errorf("请求失败: %v", err) } defer resp.Body.Close() - + if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("HTTP状态错误: %v", resp.Status) } - + body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("读取响应失败: %v", err) } - + return string(body), nil } @@ -189,7 +186,7 @@ func ContainsAllIgnoreBRepeats(a, b []string) bool { for _, item := range a { setA[item] = struct{}{} } - + // 遍历 B 的唯一元素,判断是否在 A 中 seen := make(map[string]struct{}) for _, item := range b { @@ -207,19 +204,19 @@ func ContainsAllIgnoreBRepeats(a, b []string) bool { // 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 } diff --git a/backend/route/route.go b/backend/route/route.go index 2a7f9b5..012985d 100644 --- a/backend/route/route.go +++ b/backend/route/route.go @@ -8,7 +8,7 @@ import ( func Register(r *gin.Engine) { v1 := r.Group("/v1") - + login := v1.Group("/login") { login.POST("/sign", api.Sign) @@ -44,6 +44,12 @@ func Register(r *gin.Engine) { access.POST("/upd_access", api.UpdateAccess) access.POST("/get_all", api.GetAllAccess) access.POST("/test_access", api.TestAccess) + + access.POST("/get_eab_list", api.GetEABList) + access.POST("/add_eab", api.AddEAB) + access.POST("/del_eab", api.DelEAB) + access.POST("/upd_eab", api.UpdEAB) + access.POST("/get_all_eab", api.GetAllEAB) } cert := v1.Group("/cert") { @@ -71,7 +77,7 @@ func Register(r *gin.Engine) { { overview.POST("/get_overviews", api.GetOverview) } - + // 1. 提供静态文件服务 r.StaticFS("/static", http.Dir("./frontend/static")) // 静态资源路径 r.StaticFS("/auto-deploy/static", http.Dir("./frontend/static")) // 静态资源路径 @@ -79,7 +85,7 @@ func Register(r *gin.Engine) { r.GET("/favicon.ico", func(c *gin.Context) { c.File("./frontend/favicon.ico") }) - + // 3. 前端路由托管:匹配所有其他路由并返回 index.html r.NoRoute(func(c *gin.Context) { c.File("./frontend/index.html") diff --git a/backend/scheduler/site_monitor.go b/backend/scheduler/site_monitor.go index ab73804..7fb8ecf 100644 --- a/backend/scheduler/site_monitor.go +++ b/backend/scheduler/site_monitor.go @@ -83,8 +83,8 @@ func SiteMonitor() { os.Remove(path) } }() - wg.Wait() } } } + wg.Wait() }