代码同步

This commit is contained in:
zhangchenhao
2025-05-10 16:45:46 +08:00
parent ad6b3cfa64
commit 8a9d766b50
103 changed files with 967 additions and 762 deletions

View File

@@ -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())

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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(`<html>
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
}

View File

@@ -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("表为空,插入默认数据...")

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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)
// }
// }
// }