mirror of https://github.com/allinssl/allinssl
【修复】长期持有tcp连接未关闭
【新增】支持通过webhook调用自己的服务解析dns记录 【新增】支持通过webhook推送证书和密钥 【新增】导入导出工作流、通知、证书、api授权数据 【新增】支持自定义插件目录pull/348/head
parent
e939724f37
commit
6d0732fc31
|
@ -4,6 +4,8 @@ import (
|
||||||
"ALLinSSL/backend/internal/setting"
|
"ALLinSSL/backend/internal/setting"
|
||||||
"ALLinSSL/backend/public"
|
"ALLinSSL/backend/public"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetSetting(c *gin.Context) {
|
func GetSetting(c *gin.Context) {
|
||||||
|
@ -47,3 +49,40 @@ func GetVersion(c *gin.Context) {
|
||||||
}
|
}
|
||||||
public.SuccessData(c, data, 0)
|
public.SuccessData(c, data, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DownloadData(c *gin.Context) {
|
||||||
|
dbPath := "data/data.db"
|
||||||
|
dbName := filepath.Base(dbPath)
|
||||||
|
|
||||||
|
// 设置响应头,让浏览器下载文件
|
||||||
|
c.Header("Content-Type", "application/octet-stream")
|
||||||
|
c.Header("Content-Disposition", "attachment; filename=\""+dbName+"\"")
|
||||||
|
c.File(dbPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UploadData(c *gin.Context) {
|
||||||
|
file, err := c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
public.FailMsg(c, "文件上传失败: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 检查文件类型
|
||||||
|
if filepath.Ext(file.Filename) != ".db" {
|
||||||
|
public.FailMsg(c, "只允许上传 .db 文件")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 备份源文件
|
||||||
|
// 修改源文件名为 data.db.bak
|
||||||
|
err = os.Rename("data/data.db", "data/data.db.bak")
|
||||||
|
if err != nil {
|
||||||
|
public.FailMsg(c, "备份源文件失败: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.SaveUploadedFile(file, "data/data.db"); err != nil {
|
||||||
|
public.FailMsg(c, "保存文件失败: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
public.SuccessMsg(c, "数据上传成功")
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,9 @@ import (
|
||||||
"ALLinSSL/backend/internal/access"
|
"ALLinSSL/backend/internal/access"
|
||||||
"ALLinSSL/backend/internal/cert"
|
"ALLinSSL/backend/internal/cert"
|
||||||
"ALLinSSL/backend/internal/cert/apply/lego/jdcloud"
|
"ALLinSSL/backend/internal/cert/apply/lego/jdcloud"
|
||||||
|
"ALLinSSL/backend/internal/cert/apply/lego/webhook"
|
||||||
"ALLinSSL/backend/public"
|
"ALLinSSL/backend/public"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
azcorecloud "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
|
azcorecloud "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
|
||||||
|
@ -202,6 +204,10 @@ func GetDNSProvider(providerName string, creds map[string]string, httpClient *ht
|
||||||
config.SecretKey = creds["secret_key"]
|
config.SecretKey = creds["secret_key"]
|
||||||
config.PropagationTimeout = maxWait
|
config.PropagationTimeout = maxWait
|
||||||
return constellix.NewDNSProviderConfig(config)
|
return constellix.NewDNSProviderConfig(config)
|
||||||
|
case "webhook":
|
||||||
|
config := webhook.NewConfig(creds)
|
||||||
|
config.PropagationTimeout = maxWait
|
||||||
|
return webhook.NewDNSProviderConfig(config)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("不支持的 DNS Provider: %s", providerName)
|
return nil, fmt.Errorf("不支持的 DNS Provider: %s", providerName)
|
||||||
|
@ -495,7 +501,9 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) {
|
||||||
}
|
}
|
||||||
httpClient = &http.Client{
|
httpClient = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
Proxy: http.ProxyURL(proxyURL),
|
Proxy: http.ProxyURL(proxyURL),
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
DisableKeepAlives: true,
|
||||||
},
|
},
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
package webhook
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ALLinSSL/backend/public"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configData string
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
WebhookConfig *public.WebhookConfig
|
||||||
|
|
||||||
|
PropagationTimeout time.Duration
|
||||||
|
PollingInterval time.Duration
|
||||||
|
TTL int
|
||||||
|
HTTPTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSProvider struct {
|
||||||
|
config *Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig(WebhookConfigStr map[string]string) *Config {
|
||||||
|
fmt.Println(WebhookConfigStr)
|
||||||
|
|
||||||
|
WebhookConfig := &public.WebhookConfig{
|
||||||
|
Url: WebhookConfigStr["url"],
|
||||||
|
Data: WebhookConfigStr["data"],
|
||||||
|
Method: WebhookConfigStr["method"],
|
||||||
|
Headers: WebhookConfigStr["headers"],
|
||||||
|
IgnoreSSL: WebhookConfigStr["ignore_ssl"] == "true",
|
||||||
|
}
|
||||||
|
fmt.Println(WebhookConfig.Url)
|
||||||
|
|
||||||
|
return &Config{
|
||||||
|
WebhookConfig: WebhookConfig,
|
||||||
|
TTL: 600,
|
||||||
|
PropagationTimeout: dns01.DefaultPropagationTimeout,
|
||||||
|
PollingInterval: dns01.DefaultPollingInterval,
|
||||||
|
HTTPTimeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, fmt.Errorf("配置不能为空")
|
||||||
|
}
|
||||||
|
return &DNSProvider{config: config}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||||
|
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
|
fmt.Println(d.config.WebhookConfig.Url)
|
||||||
|
configData = d.config.WebhookConfig.Data
|
||||||
|
return d.send(domain, token, keyAuth, "present")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
|
d.config.WebhookConfig.Data = configData
|
||||||
|
return d.send(domain, token, keyAuth, "cleanup")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNSProvider) send(domain, token, keyAuth, action string) error {
|
||||||
|
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||||
|
|
||||||
|
data, err := public.ReplaceJSONPlaceholders(d.config.WebhookConfig.Data, map[string]interface{}{"domain": info.EffectiveFQDN, "token": token, "keyAuth": info.Value, "action": action})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("替换JSON占位符失败: %w", err)
|
||||||
|
}
|
||||||
|
d.config.WebhookConfig.Data = data
|
||||||
|
return d.config.WebhookConfig.Send()
|
||||||
|
}
|
|
@ -91,7 +91,8 @@ func Request1panel(data *map[string]any, method, providerID, requestUrl string)
|
||||||
ignoreSsl = true
|
ignoreSsl = true
|
||||||
}
|
}
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||||
|
DisableKeepAlives: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{Transport: tr}
|
client := &http.Client{Transport: tr}
|
||||||
|
@ -269,7 +270,7 @@ func OnePanelSiteList(providerID string) ([]response.AccessSiteList, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("获取网站列表失败 %v", err)
|
return nil, fmt.Errorf("获取网站列表失败 %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var result []response.AccessSiteList
|
var result []response.AccessSiteList
|
||||||
sites, ok := siteList["data"].(map[string]any)["items"].([]any)
|
sites, ok := siteList["data"].(map[string]any)["items"].([]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -65,7 +65,8 @@ func RequestBt(data *url.Values, method, providerID, requestUrl string) (map[str
|
||||||
ignoreSsl = true
|
ignoreSsl = true
|
||||||
}
|
}
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||||
|
DisableKeepAlives: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{Transport: tr}
|
client := &http.Client{Transport: tr}
|
||||||
|
|
|
@ -65,7 +65,8 @@ func RequestBtWaf(data *map[string]any, method, providerID, requestUrl string) (
|
||||||
ignoreSsl = true
|
ignoreSsl = true
|
||||||
}
|
}
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||||
|
DisableKeepAlives: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{Transport: tr}
|
client := &http.Client{Transport: tr}
|
||||||
|
@ -207,4 +208,4 @@ func BtWafAPITest(providerID string) error {
|
||||||
return fmt.Errorf("测试请求失败: %v", err)
|
return fmt.Errorf("测试请求失败: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"ALLinSSL/backend/internal/cert/deploy/doge"
|
"ALLinSSL/backend/internal/cert/deploy/doge"
|
||||||
"ALLinSSL/backend/internal/cert/deploy/lecdn"
|
"ALLinSSL/backend/internal/cert/deploy/lecdn"
|
||||||
"ALLinSSL/backend/internal/cert/deploy/plugin"
|
"ALLinSSL/backend/internal/cert/deploy/plugin"
|
||||||
|
"ALLinSSL/backend/internal/cert/deploy/webhook"
|
||||||
"ALLinSSL/backend/public"
|
"ALLinSSL/backend/public"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
@ -106,6 +107,9 @@ func Deploy(cfg map[string]any, logger *public.Logger) error {
|
||||||
case "plugin":
|
case "plugin":
|
||||||
logger.Debug("使用插件部署...")
|
logger.Debug("使用插件部署...")
|
||||||
return plugin.Deploy(cfg, logger)
|
return plugin.Deploy(cfg, logger)
|
||||||
|
case "webhook":
|
||||||
|
logger.Debug("通过Webhook推送证书...")
|
||||||
|
return webhook.Deploy(cfg)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("不支持的部署: %s", providerName)
|
return fmt.Errorf("不支持的部署: %s", providerName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,8 @@ func requestLecdn(url, method, token string, params map[string]any, ignoreSsl bo
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||||
|
DisableKeepAlives: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,8 @@ func RequestSafeLineWaf(data *map[string]any, method, providerID, requestUrl str
|
||||||
ignoreSsl = true
|
ignoreSsl = true
|
||||||
}
|
}
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||||
|
DisableKeepAlives: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{Transport: tr}
|
client := &http.Client{Transport: tr}
|
||||||
|
@ -211,4 +212,4 @@ func SafeLineAPITest(providerID string) error {
|
||||||
return fmt.Errorf("测试请求失败: %v", err)
|
return fmt.Errorf("测试请求失败: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package webhook
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ALLinSSL/backend/internal/access"
|
||||||
|
"ALLinSSL/backend/public"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Deploy(cfg map[string]any) error {
|
||||||
|
cert, ok := cfg["certificate"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("证书不存在")
|
||||||
|
}
|
||||||
|
var providerID string
|
||||||
|
switch v := cfg["provider_id"].(type) {
|
||||||
|
case float64:
|
||||||
|
providerID = strconv.Itoa(int(v))
|
||||||
|
case string:
|
||||||
|
providerID = v
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("参数错误:provider_id")
|
||||||
|
}
|
||||||
|
//
|
||||||
|
providerData, err := access.GetAccess(providerID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
providerConfigStr, ok := providerData["config"].(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("api配置错误")
|
||||||
|
}
|
||||||
|
// 解析 JSON 配置
|
||||||
|
var providerConfig public.WebhookConfig
|
||||||
|
err = json.Unmarshal([]byte(providerConfigStr), &providerConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
certStr, ok := cert["cert"].(string)
|
||||||
|
if !ok || certStr == "" {
|
||||||
|
return fmt.Errorf("cert is required and must be a string")
|
||||||
|
}
|
||||||
|
keyStr, ok := cert["key"].(string)
|
||||||
|
if !ok || keyStr == "" {
|
||||||
|
return fmt.Errorf("key is required and must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := public.ReplaceJSONPlaceholders(providerConfig.Data, map[string]interface{}{"key": keyStr, "cert": certStr})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("替换JSON占位符失败: %w", err)
|
||||||
|
}
|
||||||
|
providerConfig.Data = data
|
||||||
|
|
||||||
|
return providerConfig.Send()
|
||||||
|
}
|
|
@ -69,8 +69,8 @@ func Check(certs []*x509.Certificate, host string, advanceDay int) (result *Cert
|
||||||
}
|
}
|
||||||
|
|
||||||
result.CommonName = leafCert.Subject.CommonName
|
result.CommonName = leafCert.Subject.CommonName
|
||||||
result.NotBefore = leafCert.NotBefore.Format("2006-01-02 15:04:05")
|
result.NotBefore = leafCert.NotBefore.In(time.Local).Format("2006-01-02 15:04:05")
|
||||||
result.NotAfter = leafCert.NotAfter.Format("2006-01-02 15:04:05")
|
result.NotAfter = leafCert.NotAfter.In(time.Local).Format("2006-01-02 15:04:05")
|
||||||
result.DaysLeft = int(leafCert.NotAfter.Sub(time.Now()).Hours() / 24)
|
result.DaysLeft = int(leafCert.NotAfter.Sub(time.Now()).Hours() / 24)
|
||||||
result.SANs = strings.Join(leafCert.DNSNames, ",")
|
result.SANs = strings.Join(leafCert.DNSNames, ",")
|
||||||
result.SignatureAlgo = leafCert.SignatureAlgorithm.String()
|
result.SignatureAlgo = leafCert.SignatureAlgorithm.String()
|
||||||
|
@ -141,8 +141,9 @@ func CheckHttps(target string, advanceDay int) (result *CertInfo, err error) {
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
},
|
},
|
||||||
|
DisableKeepAlives: true,
|
||||||
},
|
},
|
||||||
//Timeout: 5 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送请求
|
// 发送请求
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -184,7 +183,7 @@ func NotifyWebHook(params map[string]any) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("解析配置失败: %v", err)
|
return fmt.Errorf("解析配置失败: %v", err)
|
||||||
}
|
}
|
||||||
config.Data, err = ReplaceJSONPlaceholders(config.Data, params)
|
config.Data, err = public.ReplaceJSONPlaceholders(config.Data, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("替换JSON占位符失败: %w", err)
|
return fmt.Errorf("替换JSON占位符失败: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -197,16 +196,3 @@ func NotifyWebHook(params map[string]any) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReplaceJSONPlaceholders(jsonStr string, vars map[string]any) (string, error) {
|
|
||||||
re := regexp.MustCompile(`__([a-zA-Z0-9_]+)__`)
|
|
||||||
result := re.ReplaceAllStringFunc(jsonStr, func(match string) string {
|
|
||||||
key := re.FindStringSubmatch(match)[1]
|
|
||||||
if val, ok := vars[key]; ok {
|
|
||||||
return fmt.Sprintf("%v", val) // 将 any 类型转换为字符串
|
|
||||||
}
|
|
||||||
return match // 未匹配到变量则保留原样
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package report
|
package report
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"ALLinSSL/backend/public"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -75,7 +76,7 @@ func NotifyWorkWx(params map[string]any) error {
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
msg, err := ReplaceJSONPlaceholders(config["data"], params)
|
msg, err := public.ReplaceJSONPlaceholders(config["data"], params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("替换JSON占位符失败: %v", err)
|
return fmt.Errorf("替换JSON占位符失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Setting struct {
|
type Setting struct {
|
||||||
Timeout int `json:"timeout" form:"timeout"`
|
Timeout int `json:"timeout" form:"timeout"`
|
||||||
Secure string `json:"secure" form:"secure"`
|
Secure string `json:"secure" form:"secure"`
|
||||||
Https string `json:"https" form:"https"`
|
Https string `json:"https" form:"https"`
|
||||||
Key string `json:"key" form:"key"`
|
Key string `json:"key" form:"key"`
|
||||||
Cert string `json:"cert" form:"cert"`
|
Cert string `json:"cert" form:"cert"`
|
||||||
Username string `json:"username" form:"username"`
|
Username string `json:"username" form:"username"`
|
||||||
Password string `json:"password" form:"password"`
|
Password string `json:"password" form:"password"`
|
||||||
|
PluginPath string `json:"plugin_path" form:"plugin_path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get() (Setting, error) {
|
func Get() (Setting, error) {
|
||||||
|
@ -57,6 +58,7 @@ func Get() (Setting, error) {
|
||||||
}
|
}
|
||||||
username := data[0]["username"].(string)
|
username := data[0]["username"].(string)
|
||||||
setting.Username = username
|
setting.Username = username
|
||||||
|
setting.PluginPath = public.GetSettingIgnoreError("plugin_dir")
|
||||||
return setting, nil
|
return setting, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +110,9 @@ func Save(setting *Setting) error {
|
||||||
public.TimeOut = setting.Timeout
|
public.TimeOut = setting.Timeout
|
||||||
restart = true
|
restart = true
|
||||||
}
|
}
|
||||||
|
if setting.PluginPath != "" && setting.PluginPath != public.GetSettingIgnoreError("plugin_dir") {
|
||||||
|
public.UpdateSetting("plugin_dir", setting.PluginPath)
|
||||||
|
}
|
||||||
if setting.Https != "" {
|
if setting.Https != "" {
|
||||||
if setting.Https == "1" {
|
if setting.Https == "1" {
|
||||||
if setting.Key == "" || setting.Cert == "" {
|
if setting.Key == "" || setting.Cert == "" {
|
||||||
|
@ -192,13 +197,16 @@ func GetVersion() (map[string]string, error) {
|
||||||
update := "0"
|
update := "0"
|
||||||
newVersionObj, err := http.Get("https://download.allinssl.com/version.json")
|
newVersionObj, err := http.Get("https://download.allinssl.com/version.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return map[string]string{
|
newVersionObj, err = http.Get("https://node1.allinssl.com/version.json")
|
||||||
"version": version,
|
if err != nil {
|
||||||
"new_version": version,
|
return map[string]string{
|
||||||
"update": update,
|
"version": version,
|
||||||
"log": "",
|
"new_version": version,
|
||||||
"date": "",
|
"update": update,
|
||||||
}, nil
|
"log": "",
|
||||||
|
"date": "",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
defer newVersionObj.Body.Close()
|
defer newVersionObj.Body.Close()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
package public
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WebhookConfig struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
Data string `json:"data,omitempty"`
|
||||||
|
Method string `json:"method,omitempty"`
|
||||||
|
Headers string `json:"headers,omitempty"`
|
||||||
|
IgnoreSSL bool `json:"ignore_ssl,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebhookConfig) Send() error {
|
||||||
|
// 确定HTTP方法
|
||||||
|
method := strings.ToUpper(w.Method)
|
||||||
|
if method == "" {
|
||||||
|
method = http.MethodPost // 默认使用POST方法
|
||||||
|
}
|
||||||
|
|
||||||
|
client := resty.New()
|
||||||
|
client.SetTimeout(30 * time.Second)
|
||||||
|
|
||||||
|
if w.IgnoreSSL {
|
||||||
|
client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
req := client.R()
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
if w.Headers != "" {
|
||||||
|
reqHeader, err := w.ParseHeaders(w.Headers)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("解析请求头错误: %w", err)
|
||||||
|
}
|
||||||
|
req.Header = reqHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
switch method {
|
||||||
|
case http.MethodPost:
|
||||||
|
{
|
||||||
|
contentType := req.Header.Get("application/json")
|
||||||
|
if contentType == "" {
|
||||||
|
contentType = "application/json"
|
||||||
|
}
|
||||||
|
switch contentType {
|
||||||
|
case "application/json":
|
||||||
|
req.SetHeader("Content-Type", "application/json")
|
||||||
|
var reqData interface{}
|
||||||
|
err := json.Unmarshal([]byte(w.Data), &reqData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("webhook数据解析失败err: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.SetBody(reqData)
|
||||||
|
case "application/x-www-form-urlencoded":
|
||||||
|
req.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
reqData := make(map[string]string)
|
||||||
|
err := json.Unmarshal([]byte(w.Data), &reqData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("webhook数据解析失败err: %w", err)
|
||||||
|
}
|
||||||
|
req.SetFormData(reqData)
|
||||||
|
case "multipart/form-data":
|
||||||
|
req.SetHeader("Content-Type", "multipart/form-data")
|
||||||
|
reqData := make(map[string]string)
|
||||||
|
err := json.Unmarshal([]byte(w.Data), &reqData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("webhook数据解析失败err: %w", err)
|
||||||
|
}
|
||||||
|
req.SetMultipartFormData(reqData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case http.MethodGet:
|
||||||
|
{
|
||||||
|
reqData := make(map[string]string)
|
||||||
|
err := json.Unmarshal([]byte(w.Data), &reqData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("webhook数据解析失败err: %w", err)
|
||||||
|
}
|
||||||
|
req.SetQueryParams(reqData)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("暂不支持的HTTP方法: %s", method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
resp, err := req.Execute(method, w.Url)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("webhook请求失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理响应
|
||||||
|
if resp.IsError() {
|
||||||
|
return fmt.Errorf("webhook返回错误状态码: %d, msg: %s", resp.StatusCode(), resp.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebhookConfig) ParseHeaders(headerStr string) (http.Header, error) {
|
||||||
|
headers := make(http.Header)
|
||||||
|
lines := strings.Split(headerStr, "\n")
|
||||||
|
|
||||||
|
for i, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(line, ":", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return nil, fmt.Errorf("解析请求头错误 第%d行: %s", i+1, line)
|
||||||
|
}
|
||||||
|
key := strings.TrimSpace(parts[0])
|
||||||
|
value := strings.TrimSpace(parts[1])
|
||||||
|
if key == "" || value == "" {
|
||||||
|
return nil, fmt.Errorf("请求头Key第%d行为空", i+1)
|
||||||
|
}
|
||||||
|
canonicalKey := http.CanonicalHeaderKey(key)
|
||||||
|
headers.Add(canonicalKey, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReplaceJSONPlaceholders(jsonStr string, vars map[string]any) (string, error) {
|
||||||
|
re := regexp.MustCompile(`__([a-zA-Z0-9_]+)__`)
|
||||||
|
result := re.ReplaceAllStringFunc(jsonStr, func(match string) string {
|
||||||
|
key := re.FindStringSubmatch(match)[1]
|
||||||
|
if val, ok := vars[key]; ok {
|
||||||
|
escaped := strconv.Quote(fmt.Sprintf("%v", val)) // 将 any 类型转换为字符串
|
||||||
|
return escaped[1 : len(escaped)-1]
|
||||||
|
}
|
||||||
|
return match // 未匹配到变量则保留原样
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
|
@ -98,6 +98,8 @@ func Register(r *gin.Engine) {
|
||||||
setting.POST("/shutdown", api.Shutdown)
|
setting.POST("/shutdown", api.Shutdown)
|
||||||
setting.POST("/restart", api.Restart)
|
setting.POST("/restart", api.Restart)
|
||||||
setting.POST("/get_version", api.GetVersion)
|
setting.POST("/get_version", api.GetVersion)
|
||||||
|
setting.GET("/download_data", api.DownloadData)
|
||||||
|
setting.POST("/upload_data", api.UploadData)
|
||||||
}
|
}
|
||||||
overview := v1.Group("/overview")
|
overview := v1.Group("/overview")
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue