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/public"
|
||||
"github.com/gin-gonic/gin"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func GetSetting(c *gin.Context) {
|
||||
|
@ -47,3 +49,40 @@ func GetVersion(c *gin.Context) {
|
|||
}
|
||||
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/cert"
|
||||
"ALLinSSL/backend/internal/cert/apply/lego/jdcloud"
|
||||
"ALLinSSL/backend/internal/cert/apply/lego/webhook"
|
||||
"ALLinSSL/backend/public"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
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.PropagationTimeout = maxWait
|
||||
return constellix.NewDNSProviderConfig(config)
|
||||
case "webhook":
|
||||
config := webhook.NewConfig(creds)
|
||||
config.PropagationTimeout = maxWait
|
||||
return webhook.NewDNSProviderConfig(config)
|
||||
|
||||
default:
|
||||
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{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
DisableKeepAlives: true,
|
||||
},
|
||||
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
|
||||
}
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tr}
|
||||
|
@ -269,7 +270,7 @@ func OnePanelSiteList(providerID string) ([]response.AccessSiteList, error) {
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("获取网站列表失败 %v", err)
|
||||
}
|
||||
|
||||
|
||||
var result []response.AccessSiteList
|
||||
sites, ok := siteList["data"].(map[string]any)["items"].([]any)
|
||||
if !ok {
|
||||
|
|
|
@ -65,7 +65,8 @@ func RequestBt(data *url.Values, method, providerID, requestUrl string) (map[str
|
|||
ignoreSsl = true
|
||||
}
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tr}
|
||||
|
|
|
@ -65,7 +65,8 @@ func RequestBtWaf(data *map[string]any, method, providerID, requestUrl string) (
|
|||
ignoreSsl = true
|
||||
}
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tr}
|
||||
|
@ -207,4 +208,4 @@ func BtWafAPITest(providerID string) error {
|
|||
return fmt.Errorf("测试请求失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"ALLinSSL/backend/internal/cert/deploy/doge"
|
||||
"ALLinSSL/backend/internal/cert/deploy/lecdn"
|
||||
"ALLinSSL/backend/internal/cert/deploy/plugin"
|
||||
"ALLinSSL/backend/internal/cert/deploy/webhook"
|
||||
"ALLinSSL/backend/public"
|
||||
"fmt"
|
||||
)
|
||||
|
@ -106,6 +107,9 @@ func Deploy(cfg map[string]any, logger *public.Logger) error {
|
|||
case "plugin":
|
||||
logger.Debug("使用插件部署...")
|
||||
return plugin.Deploy(cfg, logger)
|
||||
case "webhook":
|
||||
logger.Debug("通过Webhook推送证书...")
|
||||
return webhook.Deploy(cfg)
|
||||
default:
|
||||
return fmt.Errorf("不支持的部署: %s", providerName)
|
||||
}
|
||||
|
|
|
@ -53,7 +53,8 @@ func requestLecdn(url, method, token string, params map[string]any, ignoreSsl bo
|
|||
|
||||
client := &http.Client{
|
||||
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
|
||||
}
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tr}
|
||||
|
@ -211,4 +212,4 @@ func SafeLineAPITest(providerID string) error {
|
|||
return fmt.Errorf("测试请求失败: %v", err)
|
||||
}
|
||||
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.NotBefore = leafCert.NotBefore.Format("2006-01-02 15:04:05")
|
||||
result.NotAfter = leafCert.NotAfter.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.In(time.Local).Format("2006-01-02 15:04:05")
|
||||
result.DaysLeft = int(leafCert.NotAfter.Sub(time.Now()).Hours() / 24)
|
||||
result.SANs = strings.Join(leafCert.DNSNames, ",")
|
||||
result.SignatureAlgo = leafCert.SignatureAlgorithm.String()
|
||||
|
@ -141,8 +141,9 @@ func CheckHttps(target string, advanceDay int) (result *CertInfo, err error) {
|
|||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
DisableKeepAlives: true,
|
||||
},
|
||||
//Timeout: 5 * time.Second,
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
@ -184,7 +183,7 @@ func NotifyWebHook(params map[string]any) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("解析配置失败: %v", err)
|
||||
}
|
||||
config.Data, err = ReplaceJSONPlaceholders(config.Data, params)
|
||||
config.Data, err = public.ReplaceJSONPlaceholders(config.Data, params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("替换JSON占位符失败: %w", err)
|
||||
}
|
||||
|
@ -197,16 +196,3 @@ func NotifyWebHook(params map[string]any) error {
|
|||
}
|
||||
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
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"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 {
|
||||
return fmt.Errorf("替换JSON占位符失败: %v", err)
|
||||
}
|
||||
|
|
|
@ -16,13 +16,14 @@ import (
|
|||
)
|
||||
|
||||
type Setting struct {
|
||||
Timeout int `json:"timeout" form:"timeout"`
|
||||
Secure string `json:"secure" form:"secure"`
|
||||
Https string `json:"https" form:"https"`
|
||||
Key string `json:"key" form:"key"`
|
||||
Cert string `json:"cert" form:"cert"`
|
||||
Username string `json:"username" form:"username"`
|
||||
Password string `json:"password" form:"password"`
|
||||
Timeout int `json:"timeout" form:"timeout"`
|
||||
Secure string `json:"secure" form:"secure"`
|
||||
Https string `json:"https" form:"https"`
|
||||
Key string `json:"key" form:"key"`
|
||||
Cert string `json:"cert" form:"cert"`
|
||||
Username string `json:"username" form:"username"`
|
||||
Password string `json:"password" form:"password"`
|
||||
PluginPath string `json:"plugin_path" form:"plugin_path"`
|
||||
}
|
||||
|
||||
func Get() (Setting, error) {
|
||||
|
@ -57,6 +58,7 @@ func Get() (Setting, error) {
|
|||
}
|
||||
username := data[0]["username"].(string)
|
||||
setting.Username = username
|
||||
setting.PluginPath = public.GetSettingIgnoreError("plugin_dir")
|
||||
return setting, nil
|
||||
}
|
||||
|
||||
|
@ -108,6 +110,9 @@ func Save(setting *Setting) error {
|
|||
public.TimeOut = setting.Timeout
|
||||
restart = true
|
||||
}
|
||||
if setting.PluginPath != "" && setting.PluginPath != public.GetSettingIgnoreError("plugin_dir") {
|
||||
public.UpdateSetting("plugin_dir", setting.PluginPath)
|
||||
}
|
||||
if setting.Https != "" {
|
||||
if setting.Https == "1" {
|
||||
if setting.Key == "" || setting.Cert == "" {
|
||||
|
@ -192,13 +197,16 @@ func GetVersion() (map[string]string, error) {
|
|||
update := "0"
|
||||
newVersionObj, err := http.Get("https://download.allinssl.com/version.json")
|
||||
if err != nil {
|
||||
return map[string]string{
|
||||
"version": version,
|
||||
"new_version": version,
|
||||
"update": update,
|
||||
"log": "",
|
||||
"date": "",
|
||||
}, nil
|
||||
newVersionObj, err = http.Get("https://node1.allinssl.com/version.json")
|
||||
if err != nil {
|
||||
return map[string]string{
|
||||
"version": version,
|
||||
"new_version": version,
|
||||
"update": update,
|
||||
"log": "",
|
||||
"date": "",
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
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("/restart", api.Restart)
|
||||
setting.POST("/get_version", api.GetVersion)
|
||||
setting.GET("/download_data", api.DownloadData)
|
||||
setting.POST("/upload_data", api.UploadData)
|
||||
}
|
||||
overview := v1.Group("/overview")
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue