diff --git a/backend/app/api/cert.go b/backend/app/api/cert.go index e435b2d..1d05588 100644 --- a/backend/app/api/cert.go +++ b/backend/app/api/cert.go @@ -41,7 +41,7 @@ func UploadCert(c *gin.Context) { } form.Key = strings.TrimSpace(form.Key) form.Cert = strings.TrimSpace(form.Cert) - + if form.Key == "" { public.FailMsg(c, "名称不能为空") return @@ -50,12 +50,12 @@ func UploadCert(c *gin.Context) { public.FailMsg(c, "类型不能为空") return } - err = cert.UploadCert(form.Key, form.Cert) + sha256, err := cert.UploadCert(form.Key, form.Cert) if err != nil { public.FailMsg(c, err.Error()) return } - public.SuccessMsg(c, "添加成功") + public.SuccessData(c, sha256, 0) return } @@ -83,7 +83,7 @@ func DelCert(c *gin.Context) { func DownloadCert(c *gin.Context) { ID := c.Query("id") - + if ID == "" { public.FailMsg(c, "ID不能为空") return @@ -93,11 +93,11 @@ func DownloadCert(c *gin.Context) { public.FailMsg(c, err.Error()) return } - + // 构建 zip 包(内存中) buf := new(bytes.Buffer) zipWriter := zip.NewWriter(buf) - + for filename, content := range certData { if filename == "cert" || filename == "key" { writer, err := zipWriter.Create(filename + ".pem") @@ -118,10 +118,10 @@ func DownloadCert(c *gin.Context) { return } // 设置响应头 - + zipName := strings.ReplaceAll(certData["domains"], ".", "_") zipName = strings.ReplaceAll(zipName, ",", "-") - + c.Header("Content-Type", "application/zip") c.Header("Content-Disposition", "attachment; filename="+zipName+".zip") c.Data(200, "application/zip", buf.Bytes()) diff --git a/backend/internal/cert/apply/apply.go b/backend/internal/cert/apply/apply.go index d4c22d4..60b7f68 100644 --- a/backend/internal/cert/apply/apply.go +++ b/backend/internal/cert/apply/apply.go @@ -39,18 +39,18 @@ func GetDNSProvider(providerName string, creds map[string]string) (challenge.Pro config.SecretID = creds["secret_id"] config.SecretKey = creds["secret_key"] return tencentcloud.NewDNSProviderConfig(config) - + // case "cloudflare": // config := cloudflare.NewDefaultConfig() // config.AuthToken = creds["CLOUDFLARE_API_TOKEN"] // return cloudflare.NewDNSProviderConfig(config) - + case "aliyun": config := alidns.NewDefaultConfig() config.APIKey = creds["access_key"] config.SecretKey = creds["access_secret"] return alidns.NewDNSProviderConfig(config) - + default: return nil, fmt.Errorf("不支持的 DNS Provider: %s", providerName) } @@ -62,7 +62,7 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { return nil, err } defer db.Close() - + email, ok := cfg["email"].(string) if !ok { return nil, fmt.Errorf("参数错误:email") @@ -84,7 +84,11 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { default: return nil, fmt.Errorf("参数错误:provider_id") } - + domainArr := strings.Split(domains, ",") + for i := range domainArr { + domainArr[i] = strings.TrimSpace(domainArr[i]) + } + // 获取上次申请的证书 runId, ok := cfg["_runId"].(string) if !ok { @@ -114,11 +118,17 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { 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, _ := time.Parse(layout, endTimeStr) + endTime, err := time.Parse(layout, endTimeStr) + if err != nil { + continue + } diff := endTime.Sub(time.Now()).Hours() / 24 if diff > maxDays { maxDays = diff @@ -131,10 +141,10 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { if !ok || cfgEnd <= 0 { cfgEnd = 30 } - + if int(maxDays) > cfgEnd { // 证书未过期,直接返回 - logger.Debug(fmt.Sprintf("上次证书申请成功,剩余天数:%d 大于%d天,已跳过申请复用此证书", 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"], @@ -145,7 +155,7 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { } } logger.Debug("正在申请证书,域名: " + domains) - + user, err := LoadUserFromDB(db, email) if err != nil { logger.Debug("acme账号不存在,注册新账号") @@ -154,10 +164,10 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { Email: email, key: privateKey, } - + config := lego.NewConfig(user) config.Certificate.KeyType = certcrypto.EC384 - + client, err := lego.NewClient(config) if err != nil { return nil, err @@ -168,14 +178,14 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { 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)) if err != nil { @@ -196,13 +206,13 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { if err != nil { return nil, err } - + // DNS 验证 provider, err := GetDNSProvider(providerStr, providerConfig) if err != nil { return nil, fmt.Errorf("创建 DNS provider 失败: %v", err) } - + err = client.Challenge.SetDNS01Provider(provider, dns01.WrapPreCheck(func(domain, fqdn, value string, check dns01.PreCheckFunc) (bool, error) { // 跳过预检查 @@ -215,29 +225,29 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { if err != nil { return nil, err } - + // fmt.Println(strings.Split(domains, ",")) request := certificate.ObtainRequest{ - Domains: strings.Split(domains, ","), + Domains: domainArr, Bundle: true, } certObj, err := client.Certificate.Obtain(request) if err != nil { return nil, err } - + certStr := string(certObj.Certificate) keyStr := string(certObj.PrivateKey) issuerCertStr := string(certObj.IssuerCertificate) - + // 保存证书和私钥 data := map[string]any{ "cert": certStr, "key": keyStr, "issuerCert": issuerCertStr, } - - err = cert.SaveCert("workflow", keyStr, certStr, issuerCertStr, runId) + + _, err = cert.SaveCert("workflow", keyStr, certStr, issuerCertStr, runId) if err != nil { return nil, err } diff --git a/backend/internal/cert/cert.go b/backend/internal/cert/cert.go index 979bf3a..c9e0473 100644 --- a/backend/internal/cert/cert.go +++ b/backend/internal/cert/cert.go @@ -26,7 +26,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 +35,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() @@ -80,7 +80,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, @@ -104,40 +104,40 @@ func AddCert(source, key, cert, issuer, issuerCert, domains, sha256, historyId, return nil } -func SaveCert(source, key, cert, issuerCert, historyId string) error { +func SaveCert(source, key, cert, issuerCert, historyId string) (string, error) { if err := public.ValidateSSLCertificate(cert, key); err != nil { - return err + return "", err } - + certObj, err := public.ParseCertificate([]byte(cert)) if err != nil { - return fmt.Errorf("解析证书失败: %v", err) + return "", fmt.Errorf("解析证书失败: %v", err) } // SHA256 sha256, err := public.GetSHA256(cert) if err != nil { - return fmt.Errorf("获取 SHA256 失败: %v", err) + return "", fmt.Errorf("获取 SHA256 失败: %v", err) } if d, _ := GetCert(sha256); d != nil { - return 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,20 +149,20 @@ func SaveCert(source, key, cert, issuerCert, historyId 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) + return "", fmt.Errorf("保存证书失败: %v", err) } - return nil + return sha256, nil } -func UploadCert(key, cert string) error { - err := SaveCert("upload", key, cert, "", "") +func UploadCert(key, cert string) (string, error) { + sha256, err := SaveCert("upload", key, cert, "", "") if err != nil { - return fmt.Errorf("保存证书失败: %v", err) + return sha256, fmt.Errorf("保存证书失败: %v", err) } - return nil + return sha256, nil } func DelCert(id string) error { @@ -171,7 +171,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 +185,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 +193,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/cert/deploy/1panel.go b/backend/internal/cert/deploy/1panel.go index 51094dc..3851622 100644 --- a/backend/internal/cert/deploy/1panel.go +++ b/backend/internal/cert/deploy/1panel.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "net/http" + "net/url" "strconv" "time" ) @@ -50,11 +51,12 @@ func Request1panel(data *map[string]any, method, providerID, requestUrl string) if err != nil { return nil, err } - if providerConfig["url"][len(providerConfig["url"])-1:] != "/" { - providerConfig["url"] += "/" + parsedURL, err := url.Parse(providerConfig["url"]) + if err != nil { + return nil, err } - - req, err := http.NewRequest(method, providerConfig["url"]+requestUrl, bytes.NewBuffer(jsonData)) + baseURL := fmt.Sprintf("%s://%s/", parsedURL.Scheme, parsedURL.Host) + req, err := http.NewRequest(method, baseURL+requestUrl, bytes.NewBuffer(jsonData)) if err != nil { // fmt.Println(err) return nil, err diff --git a/backend/internal/cert/deploy/btpanel.go b/backend/internal/cert/deploy/btpanel.go index 2b7a6aa..d689e45 100644 --- a/backend/internal/cert/deploy/btpanel.go +++ b/backend/internal/cert/deploy/btpanel.go @@ -41,14 +41,17 @@ func RequestBt(data *url.Values, method, providerID, requestUrl string) (map[str } timestamp := time.Now().Unix() token := generateSignature(fmt.Sprintf("%d", timestamp), providerConfig["api_key"]) - if providerConfig["url"][len(providerConfig["url"])-1:] != "/" { - providerConfig["url"] += "/" - } data.Set("request_time", fmt.Sprintf("%d", timestamp)) data.Set("request_token", token) - req, err := http.NewRequest(method, providerConfig["url"]+requestUrl, strings.NewReader(data.Encode())) + parsedURL, err := url.Parse(providerConfig["url"]) + if err != nil { + return nil, err + } + baseURL := fmt.Sprintf("%s://%s/", parsedURL.Scheme, parsedURL.Host) + + req, err := http.NewRequest(method, baseURL+requestUrl, strings.NewReader(data.Encode())) if err != nil { return nil, err } @@ -112,7 +115,7 @@ func DeployBt(cfg map[string]any) error { data.Set("cert_type", "1") data.Set("privateKey", keyPem) data.Set("certPem", certPem) - _, err := RequestBt(&data, "POST", providerID, "/config?action=SetPanelSSL") + _, err := RequestBt(&data, "POST", providerID, "config?action=SetPanelSSL") if err != nil { return fmt.Errorf("证书部署失败: %v", err) } @@ -150,7 +153,7 @@ func DeployBtSite(cfg map[string]any) error { data.Set("key", keyPem) data.Set("csr", certPem) data.Set("siteName", siteName) - _, err := RequestBt(&data, "POST", providerID, "/site?action=SetSSL") + _, err := RequestBt(&data, "POST", providerID, "site?action=SetSSL") if err != nil { return fmt.Errorf("证书部署失败: %v", err) } diff --git a/backend/internal/cert/deploy/deploy.go b/backend/internal/cert/deploy/deploy.go index 8487d24..d5b18a9 100644 --- a/backend/internal/cert/deploy/deploy.go +++ b/backend/internal/cert/deploy/deploy.go @@ -37,8 +37,9 @@ func Deploy(cfg map[string]any, logger *public.Logger) error { case "aliyun-cdn": logger.Debug("部署到阿里云CDN...") return DeployAliCdn(cfg) - // case "aliyun-oss": - + case "aliyun-oss": + logger.Debug("部署到阿里云OSS...") + return DeployOss(cfg) default: return fmt.Errorf("不支持的部署: %s", providerName) } diff --git a/backend/internal/cert/deploy/ssh.go b/backend/internal/cert/deploy/ssh.go index 7fbe234..ad4e331 100644 --- a/backend/internal/cert/deploy/ssh.go +++ b/backend/internal/cert/deploy/ssh.go @@ -14,7 +14,7 @@ type SSHConfig struct { Password string // 可选 PrivateKey string // 可选 Host string - Port string + Port float64 } type RemoteFile struct { @@ -24,7 +24,7 @@ type RemoteFile struct { func buildAuthMethods(password, privateKey string) ([]ssh.AuthMethod, error) { var methods []ssh.AuthMethod - + if privateKey != "" { signer, err := ssh.ParsePrivateKey([]byte(privateKey)) if err != nil { @@ -32,71 +32,71 @@ func buildAuthMethods(password, privateKey string) ([]ssh.AuthMethod, error) { } methods = append(methods, ssh.PublicKeys(signer)) } - + if password != "" { methods = append(methods, ssh.Password(password)) } - + if len(methods) == 0 { return nil, fmt.Errorf("no authentication methods provided") } - + return methods, nil } func writeMultipleFilesViaSSH(config SSHConfig, files []RemoteFile, preCmd, postCmd string) error { - addr := fmt.Sprintf("%s:%s", config.Host, config.Port) - + addr := fmt.Sprintf("%s:%d", config.Host, int(config.Port)) + authMethods, err := buildAuthMethods(config.Password, config.PrivateKey) if err != nil { return err } - + sshConfig := &ssh.ClientConfig{ User: config.User, Auth: authMethods, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } - + client, err := ssh.Dial("tcp", addr, sshConfig) if err != nil { return fmt.Errorf("failed to dial: %v", err) } defer client.Close() - + session, err := client.NewSession() if err != nil { return fmt.Errorf("会话创建失败: %v", err) } defer session.Close() - + var script bytes.Buffer - + if preCmd != "" { script.WriteString(preCmd + " && ") } - + for i, file := range files { if i > 0 { script.WriteString(" && ") } - + dirCmd := fmt.Sprintf("mkdir -p $(dirname %q)", file.Path) writeCmd := fmt.Sprintf("printf %%s '%s' > %s", file.Content, file.Path) - + script.WriteString(dirCmd + " && " + writeCmd) } - + if postCmd != "" { script.WriteString(" && " + postCmd) } - + cmd := script.String() - + if err := session.Run(cmd); err != nil { return fmt.Errorf("运行出错: %v", err) } - + return nil } @@ -127,17 +127,17 @@ func DeploySSH(cfg map[string]any) error { if !ok { return fmt.Errorf("参数错误:keyPath") } - certPath, ok := cfg["keyPath"].(string) + certPath, ok := cfg["certPath"].(string) if !ok { return fmt.Errorf("参数错误:certPath") } beforeCmd, ok := cfg["beforeCmd"].(string) if !ok { - return fmt.Errorf("参数错误:beforeCmd") + beforeCmd = "" } afterCmd, ok := cfg["afterCmd"].(string) if !ok { - return fmt.Errorf("参数错误:afterCmd") + afterCmd = "" } providerData, err := access.GetAccess(providerID) if err != nil { @@ -155,8 +155,8 @@ func DeploySSH(cfg map[string]any) error { } // 自动创建多级目录 files := []RemoteFile{ - {Path: keyPath, Content: certPem}, - {Path: certPath, Content: keyPem}, + {Path: certPath, Content: certPem}, + {Path: keyPath, Content: keyPem}, } err = writeMultipleFilesViaSSH(providerConfig, files, beforeCmd, afterCmd) if err != nil { diff --git a/backend/internal/overview/overview.go b/backend/internal/overview/overview.go index 36bf68c..4b93d84 100644 --- a/backend/internal/overview/overview.go +++ b/backend/internal/overview/overview.go @@ -15,7 +15,7 @@ func GetWorkflowCount() (map[string]any, error) { s.Connect() defer s.Close() workflow, err := s.Query(`select count(*) as count, - count(case when active=1 then 1 end ) as active, + count(case when exec_type='auto' then 1 end ) as active, count(case when last_run_status='fail' then 1 end ) as failure from workflow `) @@ -113,9 +113,9 @@ func GetWorkflowHistory() ([]map[string]any, error) { } switch v["exec_type"] { case "manual": - mode = "手动触发" + mode = "手动" case "auto": - mode = "定时触发" + mode = "自动" } wk, err := s.Where("id=?", []interface{}{v["workflow_id"]}).Select() if err != nil { @@ -126,7 +126,7 @@ func GetWorkflowHistory() ([]map[string]any, error) { } else { name = "未知" } - + result = append(result, map[string]any{ "name": name, "state": state, diff --git a/backend/internal/report/report.go b/backend/internal/report/report.go index c23b76d..e037c95 100644 --- a/backend/internal/report/report.go +++ b/backend/internal/report/report.go @@ -1,189 +1,206 @@ -package report - -import ( - "ALLinSSL/backend/public" - "crypto/tls" - "encoding/json" - "fmt" - "github.com/jordan-wright/email" - "net/smtp" - "time" -) - -func GetSqlite() (*public.Sqlite, error) { - s, err := public.NewSqlite("data/data.db", "") - if err != nil { - return nil, err - } - s.Connect() - s.TableName = "report" - return s, nil -} - -func GetList(search string, p, limit int64) ([]map[string]any, int, error) { - var data []map[string]any - var count int64 - s, err := GetSqlite() - if err != nil { - return data, 0, err - } - defer s.Close() - - var limits []int64 - if p >= 0 && limit >= 0 { - limits = []int64{0, limit} - if p > 1 { - limits[0] = (p - 1) * limit - 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() - } else { - count, err = s.Count() - data, err = s.Order("update_time", "desc").Limit(limits).Select() - } - if err != nil { - return data, 0, err - } - return data, int(count), nil -} - -func GetReport(id string) (map[string]any, error) { - s, err := GetSqlite() - if err != nil { - return nil, err - } - defer s.Close() - data, err := s.Where("id=?", []interface{}{id}).Select() - if err != nil { - return nil, err - } - if len(data) == 0 { - return nil, fmt.Errorf("没有找到此通知配置") - } - return data[0], nil - -} - -func AddReport(Type, config, name string) error { - s, err := GetSqlite() - if err != nil { - return err - } - defer s.Close() - now := time.Now().Format("2006-01-02 15:04:05") - _, err = s.Insert(map[string]interface{}{ - "name": name, - "type": Type, - "config": config, - "create_time": now, - "update_time": now, - }) - return err -} - -func UpdReport(id, config, name string) error { - s, err := GetSqlite() - if err != nil { - return err - } - defer s.Close() - _, err = s.Where("id=?", []interface{}{id}).Update(map[string]interface{}{ - "name": name, - "config": config, - }) - return err -} - -func DelReport(id string) error { - s, err := GetSqlite() - if err != nil { - return err - } - defer s.Close() - _, err = s.Where("id=?", []interface{}{id}).Delete() - return err -} - -func NotifyTest(id string) error { - if id == "" { - return fmt.Errorf("缺少参数") - } - providerData, err := GetReport(id) - if err != nil { - return err - } - params := map[string]any{ - "provider_id": id, - "body": "测试消息通道", - "subject": "测试消息通道", - } - switch providerData["type"] { - case "mail": - err = NotifyMail(params) - } - return err -} - -func Notify(params map[string]any) error { - if params == nil { - return fmt.Errorf("缺少参数") - } - providerName, ok := params["provider"].(string) - if !ok { - return fmt.Errorf("通知类型错误") - } - switch providerName { - case "mail": - return NotifyMail(params) - // case "btpanel-site": - // return NotifyBt(params) - default: - return fmt.Errorf("不支持的通知类型") - } -} - -func NotifyMail(params map[string]any) error { - - if params == nil { - return fmt.Errorf("缺少参数") - } - providerID := params["provider_id"].(string) - // fmt.Println(providerID) - providerData, err := GetReport(providerID) - if err != nil { - return err - } - configStr := providerData["config"].(string) - var config map[string]string - err = json.Unmarshal([]byte(configStr), &config) - 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{ - InsecureSkipVerify: true, // 开发阶段跳过证书验证,生产建议关闭 - ServerName: config["smtpHost"], - } - return e.SendWithTLS(addr, auth, tlsConfig) - } - - // 普通明文发送(25端口,非推荐) - return e.Send(addr, auth) -} +package report + +import ( + "ALLinSSL/backend/public" + "crypto/tls" + "encoding/json" + "fmt" + "github.com/jordan-wright/email" + "net/smtp" + "strings" + "time" +) + +func GetSqlite() (*public.Sqlite, error) { + s, err := public.NewSqlite("data/data.db", "") + if err != nil { + return nil, err + } + s.Connect() + s.TableName = "report" + return s, nil +} + +func GetList(search string, p, limit int64) ([]map[string]any, int, error) { + var data []map[string]any + var count int64 + s, err := GetSqlite() + if err != nil { + return data, 0, err + } + defer s.Close() + + var limits []int64 + if p >= 0 && limit >= 0 { + limits = []int64{0, limit} + if p > 1 { + limits[0] = (p - 1) * limit + 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() + } else { + count, err = s.Count() + data, err = s.Order("update_time", "desc").Limit(limits).Select() + } + if err != nil { + return data, 0, err + } + return data, int(count), nil +} + +func GetReport(id string) (map[string]any, error) { + s, err := GetSqlite() + if err != nil { + return nil, err + } + defer s.Close() + data, err := s.Where("id=?", []interface{}{id}).Select() + if err != nil { + return nil, err + } + if len(data) == 0 { + return nil, fmt.Errorf("没有找到此通知配置") + } + return data[0], nil + +} + +func AddReport(Type, config, name string) error { + s, err := GetSqlite() + if err != nil { + return err + } + defer s.Close() + now := time.Now().Format("2006-01-02 15:04:05") + _, err = s.Insert(map[string]interface{}{ + "name": name, + "type": Type, + "config": config, + "create_time": now, + "update_time": now, + }) + return err +} + +func UpdReport(id, config, name string) error { + s, err := GetSqlite() + if err != nil { + return err + } + defer s.Close() + _, err = s.Where("id=?", []interface{}{id}).Update(map[string]interface{}{ + "name": name, + "config": config, + }) + return err +} + +func DelReport(id string) error { + s, err := GetSqlite() + if err != nil { + return err + } + defer s.Close() + _, err = s.Where("id=?", []interface{}{id}).Delete() + return err +} + +func NotifyTest(id string) error { + if id == "" { + return fmt.Errorf("缺少参数") + } + providerData, err := GetReport(id) + if err != nil { + return err + } + params := map[string]any{ + "provider_id": id, + "body": "测试消息通道", + "subject": "测试消息通道", + } + switch providerData["type"] { + case "mail": + err = NotifyMail(params) + } + return err +} + +func Notify(params map[string]any) error { + if params == nil { + return fmt.Errorf("缺少参数") + } + providerName, ok := params["provider"].(string) + if !ok { + return fmt.Errorf("通知类型错误") + } + switch providerName { + case "mail": + return NotifyMail(params) + // case "btpanel-site": + // return NotifyBt(params) + default: + return fmt.Errorf("不支持的通知类型") + } +} + +func NotifyMail(params map[string]any) error { + + if params == nil { + return fmt.Errorf("缺少参数") + } + providerID := params["provider_id"].(string) + // fmt.Println(providerID) + providerData, err := GetReport(providerID) + if err != nil { + return err + } + configStr := providerData["config"].(string) + var config map[string]string + err = json.Unmarshal([]byte(configStr), &config) + 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{ + InsecureSkipVerify: true, // 开发阶段跳过证书验证,生产建议关闭 + ServerName: config["smtpHost"], + } + err = e.SendWithTLS(addr, auth, tlsConfig) + if err != nil { + if err.Error() == "EOF" || strings.Contains(err.Error(), "short response") || err.Error() == "server response incomplete" { + // 忽略短响应错误 + return nil + } + return err + } + return nil + } + + // 普通明文发送(25端口,非推荐) + err = e.Send(addr, auth) + if err != nil { + if err.Error() == "EOF" || strings.Contains(err.Error(), "short response") || err.Error() == "server response incomplete" { + // 忽略短响应错误 + return nil + } + return err + } + return nil +} diff --git a/backend/internal/setting/setting.go b/backend/internal/setting/setting.go index 9d2b865..ff2f6b1 100644 --- a/backend/internal/setting/setting.go +++ b/backend/internal/setting/setting.go @@ -95,34 +95,37 @@ func Save(setting *Setting) error { reload = true } s.TableName = "settings" - if setting.Timeout != 0 { + if setting.Timeout != 0 && setting.Timeout != public.TimeOut { s.Where("key = 'timeout'", []interface{}{}).Update(map[string]interface{}{"value": setting.Timeout}) public.TimeOut = setting.Timeout - } - if setting.Secure != "" { - s.Where("key = 'secure'", []interface{}{}).Update(map[string]interface{}{"value": setting.Secure}) - public.TimeOut = setting.Timeout - } - if setting.Https == "1" { - if setting.Key == "" || setting.Cert == "" { - return fmt.Errorf("key or cert is empty") - } - // fmt.Println(setting.Key, setting.Cert) - err := public.ValidateSSLCertificate(setting.Cert, setting.Key) - if err != nil { - return err - } - s.Where("key = 'https'", []interface{}{}).Update(map[string]interface{}{"value": setting.Https}) - // dir := filepath.Dir("data/https") - if err := os.MkdirAll("data/https", os.ModePerm); err != nil { - panic("创建目录失败: " + err.Error()) - } - err = os.WriteFile("data/https/key.pem", []byte(setting.Key), 0644) - // fmt.Println(err) - os.WriteFile("data/https/cert.pem", []byte(setting.Cert), 0644) restart = true } - + if setting.Secure != "" && setting.Secure != public.Secure { + s.Where("key = 'secure'", []interface{}{}).Update(map[string]interface{}{"value": setting.Secure}) + public.TimeOut = setting.Timeout + restart = true + } + if setting.Https != "" && setting.Https != public.GetSettingIgnoreError("https") { + if setting.Https == "1" { + if setting.Key == "" || setting.Cert == "" { + return fmt.Errorf("key or cert is empty") + } + // fmt.Println(setting.Key, setting.Cert) + err := public.ValidateSSLCertificate(setting.Cert, setting.Key) + if err != nil { + return err + } + // dir := filepath.Dir("data/https") + if err := os.MkdirAll("data/https", os.ModePerm); err != nil { + panic("创建目录失败: " + err.Error()) + } + err = os.WriteFile("data/https/key.pem", []byte(setting.Key), 0644) + // fmt.Println(err) + os.WriteFile("data/https/cert.pem", []byte(setting.Cert), 0644) + } + s.Where("key = 'https'", []interface{}{}).Update(map[string]interface{}{"value": setting.Https}) + restart = true + } if restart { Restart() return nil diff --git a/backend/internal/workflow/executor.go b/backend/internal/workflow/executor.go index 8633bb1..00ada84 100644 --- a/backend/internal/workflow/executor.go +++ b/backend/internal/workflow/executor.go @@ -8,6 +8,7 @@ import ( "ALLinSSL/backend/public" "errors" "fmt" + "strconv" ) // var executors map[string]func(map[string]any) (any, error) @@ -33,7 +34,7 @@ func Executors(exec string, params map[string]any) (any, error) { func apply(params map[string]any) (any, error) { logger := params["logger"].(*public.Logger) - + logger.Info("=============申请证书=============") certificate, err := certApply.Apply(params, logger) if err != nil { @@ -67,28 +68,57 @@ func deploy(params map[string]any) (any, error) { func upload(params map[string]any) (any, error) { logger := params["logger"].(*public.Logger) logger.Info("=============上传证书=============") - - keyStr, ok := params["key"].(string) - if !ok { - logger.Error("上传的密钥有误") - logger.Info("=============上传失败=============") - return nil, errors.New("上传的密钥有误") + // 判断证书id走本地还是走旧上传,应在之后的迭代中移除旧代码 + if params["cert_id"] == nil { + keyStr, ok := params["key"].(string) + if !ok { + logger.Error("上传的密钥有误") + logger.Info("=============上传失败=============") + return nil, errors.New("上传的密钥有误") + } + certStr, ok := params["cert"].(string) + if !ok { + logger.Error("上传的证书有误") + logger.Info("=============上传失败=============") + return nil, errors.New("上传的证书有误") + } + _, err := cert.UploadCert(keyStr, certStr) + if err != nil { + logger.Error(err.Error()) + logger.Info("=============上传失败=============") + return nil, err + } + logger.Info("=============上传成功=============") + + return params, nil + } else { + certId := "" + switch v := params["cert_id"].(type) { + case float64: + certId = strconv.Itoa(int(v)) + case string: + certId = v + default: + logger.Info("=============上传证书获取失败=============") + return nil, errors.New("证书 ID 类型错误") + } + result := map[string]any{} + certObj, err := cert.GetCert(certId) + if err != nil { + logger.Error(err.Error()) + logger.Info("=============上传证书获取失败=============") + return nil, err + } + if certObj == nil { + logger.Error("证书不存在") + logger.Info("=============上传证书获取失败=============") + return nil, errors.New("证书不存在") + } + logger.Debug(fmt.Sprintf("证书 ID: %s", certId)) + result["cert"] = certObj["cert"] + result["key"] = certObj["key"] + return result, nil } - certStr, ok := params["cert"].(string) - if !ok { - logger.Error("上传的证书有误") - logger.Info("=============上传失败=============") - return nil, errors.New("上传的证书有误") - } - err := cert.UploadCert(keyStr, certStr) - if err != nil { - logger.Error(err.Error()) - logger.Info("=============上传失败=============") - return nil, err - } - logger.Info("=============上传成功=============") - - return params, nil } func notify(params map[string]any) (any, error) { diff --git a/backend/internal/workflow/workflow.go b/backend/internal/workflow/workflow.go index 2f4f692..8d63876 100644 --- a/backend/internal/workflow/workflow.go +++ b/backend/internal/workflow/workflow.go @@ -4,7 +4,6 @@ import ( "ALLinSSL/backend/public" "encoding/json" "fmt" - "strings" "sync" "time" ) @@ -27,7 +26,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} @@ -36,7 +35,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() @@ -56,7 +55,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 @@ -161,7 +160,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) @@ -192,13 +191,15 @@ func resolveInputs(inputs []WorkflowNodeParams, ctx *ExecutionContext) map[strin for _, input := range inputs { if input.FromNodeID != "" { if val, ok := ctx.GetOutput(input.FromNodeID); ok { - switch strings.Split(strings.TrimPrefix(input.FromNodeID, "-"), "-")[0] { - case "apply": - input.Name = "certificate" - case "upload": - input.Name = "certificate" - } - resolved[input.Name] = val + // 暂时没有新的类型可以先写死 + // switch strings.Split(strings.TrimPrefix(input.FromNodeID, "-"), "-")[0] { + // case "apply": + // input.Name = "certificate" + // case "upload": + // input.Name = "certificate" + // } + // resolved[input.Name] = val + resolved["certificate"] = val } } } @@ -217,10 +218,10 @@ func RunNode(node *WorkflowNode, ctx *ExecutionContext) error { } node.Config["_runId"] = ctx.RunID node.Config["logger"] = ctx.Logger - + // 执行当前节点 result, err := Executors(node.Type, node.Config) - + var status ExecutionStatus if err != nil { status = StatusFailed @@ -230,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 { @@ -268,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 e7ae9e4..8c8bdac 100644 --- a/backend/internal/workflow/workflow_history.go +++ b/backend/internal/workflow/workflow_history.go @@ -1,117 +1,117 @@ -package workflow - -import ( - "ALLinSSL/backend/public" - "os" - "path/filepath" - "time" -) - -// GetSqliteObjWH 工作流执行历史记录表对象 -func GetSqliteObjWH() (*public.Sqlite, error) { - s, err := public.NewSqlite("data/data.db", "") - if err != nil { - return nil, err - } - s.Connect() - s.TableName = "workflow_history" - return s, nil -} - -// GetListWH 获取工作流执行历史记录列表 -func GetListWH(id string, p, limit int64) ([]map[string]any, int, error) { - var data []map[string]any - var count int64 - s, err := GetSqliteObjWH() - if err != nil { - return data, 0, err - } - defer s.Close() - - var limits []int64 - if p >= 0 && limit >= 0 { - limits = []int64{0, limit} - if p > 1 { - limits[0] = (p - 1) * limit - limits[1] = p * limit - } - } - if id == "" { - count, err = s.Count() - data, err = s.Limit(limits).Order("create_time", "desc").Select() - } else { - 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 - } - return data, int(count), nil -} - -// 添加工作流执行历史记录 -func AddWorkflowHistory(workflowID, execType string) (string, error) { - s, err := GetSqliteObjWH() - if err != nil { - return "", err - } - defer s.Close() - now := time.Now().Format("2006-01-02 15:04:05") - ID := public.GenerateUUID() - _, err = s.Insert(map[string]interface{}{ - "id": ID, - "workflow_id": workflowID, - "status": "running", - "exec_type": execType, - "create_time": now, - }) - if err != nil { - return "", err - } - _ = UpdDb(workflowID, map[string]interface{}{"last_run_status": "running", "last_run_time": now}) - return ID, nil -} - -// 工作流执行结束 -func UpdateWorkflowHistory(id, status string) error { - s, err := GetSqliteObjWH() - if err != nil { - return err - } - defer s.Close() - now := time.Now().Format("2006-01-02 15:04:05") - _, err = s.Where("id=?", []interface{}{id}).Update(map[string]interface{}{ - "status": status, - "end_time": now, - }) - if err != nil { - return err - } - return nil -} - -func StopWorkflow(id string) error { - s, err := GetSqliteObjWH() - if err != nil { - return err - } - defer s.Close() - data, err := s.Where("id=?", []interface{}{id}).Select() - if err != nil { - return err - } - if len(data) == 0 { - return nil - } - SetWorkflowStatus(data[0]["workflow_id"].(string), id, "fail") - return nil -} - -func GetExecLog(id string) (string, error) { - log, err := os.ReadFile(filepath.Join(public.GetSettingIgnoreError("workflow_log_path"), id+".log")) - if err != nil { - return "", err - } - return string(log), nil -} +package workflow + +import ( + "ALLinSSL/backend/public" + "os" + "path/filepath" + "time" +) + +// GetSqliteObjWH 工作流执行历史记录表对象 +func GetSqliteObjWH() (*public.Sqlite, error) { + s, err := public.NewSqlite("data/data.db", "") + if err != nil { + return nil, err + } + s.Connect() + s.TableName = "workflow_history" + return s, nil +} + +// GetListWH 获取工作流执行历史记录列表 +func GetListWH(id string, p, limit int64) ([]map[string]any, int, error) { + var data []map[string]any + var count int64 + s, err := GetSqliteObjWH() + if err != nil { + return data, 0, err + } + defer s.Close() + + var limits []int64 + if p >= 0 && limit >= 0 { + limits = []int64{0, limit} + if p > 1 { + limits[0] = (p - 1) * limit + limits[1] = p * limit + } + } + if id == "" { + count, err = s.Count() + data, err = s.Limit(limits).Order("create_time", "desc").Select() + } else { + 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 + } + return data, int(count), nil +} + +// 添加工作流执行历史记录 +func AddWorkflowHistory(workflowID, execType string) (string, error) { + s, err := GetSqliteObjWH() + if err != nil { + return "", err + } + defer s.Close() + now := time.Now().Format("2006-01-02 15:04:05") + ID := public.GenerateUUID() + _, err = s.Insert(map[string]interface{}{ + "id": ID, + "workflow_id": workflowID, + "status": "running", + "exec_type": execType, + "create_time": now, + }) + if err != nil { + return "", err + } + _ = UpdDb(workflowID, map[string]interface{}{"last_run_status": "running", "last_run_time": now}) + return ID, nil +} + +// 工作流执行结束 +func UpdateWorkflowHistory(id, status string) error { + s, err := GetSqliteObjWH() + if err != nil { + return err + } + defer s.Close() + now := time.Now().Format("2006-01-02 15:04:05") + _, err = s.Where("id=?", []interface{}{id}).Update(map[string]interface{}{ + "status": status, + "end_time": now, + }) + if err != nil { + return err + } + return nil +} + +func StopWorkflow(id string) error { + s, err := GetSqliteObjWH() + if err != nil { + return err + } + defer s.Close() + data, err := s.Where("id=?", []interface{}{id}).Select() + if err != nil { + return err + } + if len(data) == 0 { + return nil + } + SetWorkflowStatus(data[0]["workflow_id"].(string), id, "fail") + return nil +} + +func GetExecLog(id string) (string, error) { + log, err := os.ReadFile(filepath.Join(public.GetSettingIgnoreError("workflow_log_path"), id+".log")) + if err != nil { + return "", err + } + return string(log), nil +} diff --git a/backend/middleware/auth.go b/backend/middleware/auth.go index 2ed0b69..7d12d35 100644 --- a/backend/middleware/auth.go +++ b/backend/middleware/auth.go @@ -2,10 +2,13 @@ package middleware import ( "ALLinSSL/backend/public" + "crypto/md5" "encoding/gob" + "encoding/hex" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "net/http" + "strconv" "strings" "time" ) @@ -20,6 +23,10 @@ var Html404 = []byte(` func SessionAuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { + if checkApiKey(c) { + return + } + routePath := c.Request.URL.Path method := c.Request.Method paths := strings.Split(strings.TrimPrefix(routePath, "/"), "/") @@ -28,12 +35,14 @@ func SessionAuthMiddleware() gin.HandlerFunc { gob.Register(time.Time{}) last := session.Get("lastRequestTime") - if routePath == public.Secure && session.Get("secure") == nil { - // 访问安全入口,设置 session - session.Set("secure", true) - session.Set("lastRequestTime", now) - // 一定要保存 session BEFORE redirect - session.Save() + if routePath == public.Secure { + if session.Get("secure") == nil { + // 访问安全入口,设置 session + session.Set("secure", true) + session.Set("lastRequestTime", now) + // 一定要保存 session BEFORE redirect + session.Save() + } // 返回登录页 c.Redirect(http.StatusFound, "/login") // c.Abort() @@ -73,6 +82,9 @@ func SessionAuthMiddleware() gin.HandlerFunc { return } } + if routePath == "/favicon.ico" { + return + } // 判断是否为静态文件路径 if method == "GET" { if len(paths) > 1 && paths[0] == "static" { @@ -86,14 +98,21 @@ func SessionAuthMiddleware() gin.HandlerFunc { return } else { if session.Get("__login_key") != public.GetSettingIgnoreError("login_key") { - session.Clear() + // session.Set("secure", true) + session.Set("login", nil) session.Save() - c.JSON(http.StatusUnauthorized, gin.H{"message": "登录信息发生变化,请重新登录"}) - c.Abort() + // c.JSON(http.StatusUnauthorized, gin.H{"message": "登录信息发生变化,请重新登录"}) + c.Redirect(http.StatusFound, "/login") + // c.Abort() } else { // 访问正常,更新最后请求时间 session.Set("lastRequestTime", now) session.Save() + if paths[0] == "login" { + c.Redirect(http.StatusFound, "/") + c.Abort() + return + } } } } @@ -106,3 +125,52 @@ func SessionAuthMiddleware() gin.HandlerFunc { } } } + +func checkApiKey(c *gin.Context) bool { + var form struct { + ApiToken string `form:"api_token"` + Timestamp string `form:"timestamp"` + } + err := c.Bind(&form) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) + c.Abort() + return false + } + if form.ApiToken == "" || form.Timestamp == "" { + return false + } + apiKey := public.GetSettingIgnoreError("api_key") + if apiKey == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "未开启api"}) + c.Abort() + return false + } + // timestamp := time.Now().Unix() + ApiToken := generateSignature(form.Timestamp, apiKey) + if form.ApiToken != ApiToken { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"}) + c.Abort() + return false + } + // 这里可以添加其他的验证逻辑,比如检查时间戳是否过期等 + timestamp, err := strconv.ParseInt(form.Timestamp, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid timestamp"}) + return false + } + if time.Now().Unix()-timestamp > 60*5 { + c.JSON(http.StatusUnauthorized, gin.H{"error": "timestamp expired"}) + return false + } + return true +} + +func generateSignature(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 +} diff --git a/backend/migrations/init.go b/backend/migrations/init.go index fc0e9e6..52ae666 100644 --- a/backend/migrations/init.go +++ b/backend/migrations/init.go @@ -4,18 +4,18 @@ import ( "ALLinSSL/backend/public" "database/sql" "fmt" - _ "github.com/mattn/go-sqlite3" + _ "modernc.org/sqlite" "os" "path/filepath" ) func init() { os.MkdirAll("data", os.ModePerm) - + dbPath := "data/data.db" _, _ = filepath.Abs(dbPath) // fmt.Println("数据库路径:", absPath) - db, err := sql.Open("sqlite3", dbPath) + db, err := sql.Open("sqlite", dbPath) if err != nil { // fmt.Println("创建数据库失败:", err) return @@ -176,15 +176,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); @@ -194,7 +194,7 @@ 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) } @@ -206,7 +206,7 @@ func insertDefaultData(db *sql.DB, table, insertSQL string) { // fmt.Println("检查数据行数失败:", err) return } - + // 如果表为空,则插入默认数据 if count == 0 { // fmt.Println("表为空,插入默认数据...") diff --git a/backend/public/utils.go b/backend/public/utils.go index d318ccc..811b86e 100644 --- a/backend/public/utils.go +++ b/backend/public/utils.go @@ -1,181 +1,202 @@ -package public - -import ( - "crypto/rand" - "fmt" - "github.com/google/uuid" - "io" - "math/big" - "net" - "net/http" - "strings" -) - -const defaultCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - -// GetSettingIgnoreError 获取系统配置-忽略错误 -func GetSettingIgnoreError(key string) string { - s, err := NewSqlite("data/data.db", "") - if err != nil { - return "" - } - s.Connect() - defer s.Close() - s.TableName = "settings" - res, err := s.Where("key=?", []interface{}{key}).Select() - if err != nil { - return "" - } - if len(res) == 0 { - return "" - } - setting, ok := res[0]["value"].(string) - if !ok { - return "" - } - return setting -} - -func UpdateSetting(key, val string) error { - s, err := NewSqlite("data/data.db", "") - 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}) - if err != nil { - return err - } - return nil -} - -func GetSettingsFromType(typ string) ([]map[string]any, error) { - db := "data/data.db" - s, err := NewSqlite(db, "") - 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 -} - -// GetFreePort 获取一个可用的随机端口 -func GetFreePort() (int, error) { - // 端口为 0,表示让系统自动分配一个可用端口 - ln, err := net.Listen("tcp", "localhost:0") - if err != nil { - 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 -} - -// RandomString 生成指定长度的随机字符串 -func RandomString(length int) string { - if str, err := RandomStringWithCharset(length, defaultCharset); err != nil { - return "allinssl" - } else { - return str - } -} - -// RandomStringWithCharset 使用指定字符集生成随机字符串 -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 { - return "", err - } - result[i] = charset[num.Int64()] - } - - return string(result), nil -} - -// GenerateUUID 生成 UUID -func GenerateUUID() string { - // 生成一个新的 UUID - uuidStr := strings.ReplaceAll(uuid.New().String(), "-", "") - - // 返回 UUID 的字符串表示 - return uuidStr -} - -func GetLocalIP() (string, error) { - interfaces, err := net.Interfaces() - if err != nil { - return "", err - } - - for _, iface := range interfaces { - if iface.Flags&net.FlagUp == 0 { - continue // 接口未启用 - } - 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) { - case *net.IPNet: - ip = v.IP - case *net.IPAddr: - ip = v.IP - } - - // 只返回 IPv4 内网地址 - if ip != nil && ip.To4() != nil && !ip.IsLoopback() { - return ip.String(), nil - } - } - } - - return "", fmt.Errorf("没有找到内网 IP") -} - -func GetPublicIP() (string, error) { - resp, err := http.Get("https://www.bt.cn/Api/getIpAddress") - if err != nil { - 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 -} +package public + +import ( + "crypto/rand" + "fmt" + "github.com/google/uuid" + "io" + "math/big" + "net" + "net/http" + "strings" +) + +const defaultCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +// GetSettingIgnoreError 获取系统配置-忽略错误 +func GetSettingIgnoreError(key string) string { + s, err := NewSqlite("data/data.db", "") + if err != nil { + return "" + } + s.Connect() + defer s.Close() + s.TableName = "settings" + res, err := s.Where("key=?", []interface{}{key}).Select() + if err != nil { + return "" + } + if len(res) == 0 { + return "" + } + setting, ok := res[0]["value"].(string) + if !ok { + return "" + } + return setting +} + +func UpdateSetting(key, val string) error { + s, err := NewSqlite("data/data.db", "") + 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}) + if err != nil { + return err + } + return nil +} + +func GetSettingsFromType(typ string) ([]map[string]any, error) { + db := "data/data.db" + s, err := NewSqlite(db, "") + 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 +} + +// GetFreePort 获取一个可用的随机端口 +func GetFreePort() (int, error) { + // 端口为 0,表示让系统自动分配一个可用端口 + ln, err := net.Listen("tcp", "localhost:0") + if err != nil { + 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 +} + +// RandomString 生成指定长度的随机字符串 +func RandomString(length int) string { + if str, err := RandomStringWithCharset(length, defaultCharset); err != nil { + return "allinssl" + } else { + return str + } +} + +// RandomStringWithCharset 使用指定字符集生成随机字符串 +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 { + return "", err + } + result[i] = charset[num.Int64()] + } + + return string(result), nil +} + +// GenerateUUID 生成 UUID +func GenerateUUID() string { + // 生成一个新的 UUID + uuidStr := strings.ReplaceAll(uuid.New().String(), "-", "") + + // 返回 UUID 的字符串表示 + return uuidStr +} + +func GetLocalIP() (string, error) { + interfaces, err := net.Interfaces() + if err != nil { + return "", err + } + + for _, iface := range interfaces { + if iface.Flags&net.FlagUp == 0 { + continue // 接口未启用 + } + 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) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + + // 只返回 IPv4 内网地址 + if ip != nil && ip.To4() != nil && !ip.IsLoopback() { + return ip.String(), nil + } + } + } + + return "", fmt.Errorf("没有找到内网 IP") +} + +func GetPublicIP() (string, error) { + resp, err := http.Get("https://www.bt.cn/Api/getIpAddress") + if err != nil { + 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 +} + +func ContainsAllIgnoreBRepeats(a, b []string) bool { + // 构建 A 的集合 + setA := make(map[string]struct{}) + for _, item := range a { + setA[item] = struct{}{} + } + + // 遍历 B 的唯一元素,判断是否在 A 中 + seen := make(map[string]struct{}) + for _, item := range b { + if _, checked := seen[item]; checked { + continue + } + seen[item] = struct{}{} + if _, ok := setA[item]; !ok { + return false + } + } + return true +} diff --git a/backend/route/route.go b/backend/route/route.go index d5f29a3..503d284 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) @@ -70,11 +70,15 @@ 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")) // 静态资源路径 - + // 返回 favicon.ico + 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/scheduler.go b/backend/scheduler/scheduler.go index 398d0db..aed3062 100644 --- a/backend/scheduler/scheduler.go +++ b/backend/scheduler/scheduler.go @@ -93,37 +93,3 @@ func (s *Scheduler) loop() { } } } - -// package scheduler -// -// import ( -// "sync" -// "time" -// ) -// -// var funcs = []func(){ -// SiteMonitor, -// RunWorkflows, -// } -// -// func Scheduler() { -// for { -// start := time.Now() -// -// var wg sync.WaitGroup -// wg.Add(len(funcs)) -// -// for _, f := range funcs { -// go func(fn func()) { -// defer wg.Done() -// fn() -// }(f) -// } -// wg.Wait() -// // 保证每轮间隔至少10秒 -// elapsed := time.Since(start) -// if elapsed < 10*time.Second { -// time.Sleep(10*time.Second - elapsed) -// } -// } -// } diff --git a/build/index.html b/build/index.html index cd99189..46b3786 100644 --- a/build/index.html +++ b/build/index.html @@ -5,8 +5,8 @@