package lecdn import ( "ALLinSSL/backend/internal/access" "ALLinSSL/backend/public" "bytes" "crypto/tls" "encoding/base64" "encoding/json" "fmt" "io" "net/http" "strconv" ) // LecdnConfig 代表 LeCDN 的配置 type LecdnConfig struct { BaseURL string `json:"baseurl"` Token string `json:"token"` Username string `json:"username"` Password string `json:"password"` IgnoreSSL bool `json:"ignore_ssl"` } // NewLecdnConfig 创建一个新的 LecdnConfig 实例 func NewLecdnConfig(providerID string) (*LecdnConfig, error) { providerData, err := access.GetAccess(providerID) if err != nil { return nil, err } providerConfigStr, ok := providerData["config"].(string) if !ok { return nil, fmt.Errorf("api配置错误") } // 解析 JSON 配置 var providerConfig map[string]string err = json.Unmarshal([]byte(providerConfigStr), &providerConfig) if err != nil { return nil, err } l := &LecdnConfig{ BaseURL: providerConfig["url"], Username: providerConfig["username"], Password: providerConfig["password"], IgnoreSSL: providerConfig["ignore_ssl"] == "1", } return l, nil } // requestLecdn 发送 HTTP 请求到 LeCDN API func requestLecdn(url, method, token string, params map[string]any, ignoreSsl bool) (map[string]any, error) { var res map[string]any client := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl}, DisableKeepAlives: true, }, } jsonData, _ := json.Marshal(params) req, err := http.NewRequest(method, url, bytes.NewBuffer(jsonData)) if err != nil { return res, err } req.Header.Set("Content-Type", "application/json") if token != "" { req.Header.Set("Authorization", "Bearer "+token) req.Header.Set("cookie", "LeCDN-Client="+token) } resp, err := client.Do(req) if err != nil { return res, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return res, fmt.Errorf("请求失败,状态码:%d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { return res, err } err = json.Unmarshal(body, &res) if err != nil { return res, fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body)) } return res, nil } // client 登录 LeCDN 并获取 Token func (l *LecdnConfig) client() error { url := fmt.Sprintf("%s/prod-api/login", l.BaseURL) resp, err := requestLecdn(url, "POST", "", map[string]any{ "username": l.Username, "password": l.Password, }, l.IgnoreSSL) if err != nil { return fmt.Errorf("登录失败: %v", err) } data, ok := resp["data"].(map[string]any) if !ok { return fmt.Errorf("登录响应格式错误: %v", resp) } token, ok := data["token"].(string) if !ok { return fmt.Errorf("登录响应中未找到 token: %v", resp) } l.Token = token return nil } // GetCertList 获取证书列表 func (l *LecdnConfig) GetCertList() ([]map[string]any, error) { url := fmt.Sprintf("%s/prod-api/certificate?current_page=1&total=9999&page_size=9999", l.BaseURL) resp, err := requestLecdn(url, "GET", l.Token, nil, l.IgnoreSSL) if err != nil { return nil, fmt.Errorf("获取证书列表失败: %v", err) } data, ok := resp["data"].(map[string]any) if !ok { return nil, fmt.Errorf("获取证书列表响应格式错误: %v", resp) } list, ok := data["items"].([]any) if !ok { return nil, fmt.Errorf("获取证书列表响应中未找到 items: %v", resp) } if len(list) == 0 { return nil, fmt.Errorf("未找到任何证书") } var certs []map[string]any for _, item := range list { cert, ok := item.(map[string]any) if !ok { return nil, fmt.Errorf("证书列表项格式错误: %v", item) } certs = append(certs, cert) } return certs, nil } // UploadCert 上传证书到 LeCDN func (l *LecdnConfig) UploadCert(cert, key, name string) (int, error) { url := fmt.Sprintf("%s/prod-api/certificate", l.BaseURL) keyBase64 := base64.StdEncoding.EncodeToString([]byte(key)) certBase64 := base64.StdEncoding.EncodeToString([]byte(cert)) params := map[string]any{ "ssl_key": keyBase64, "ssl_pem": certBase64, "type": "upload", "auto_renewal": false, "name": name, } resp, err := requestLecdn(url, "POST", l.Token, params, l.IgnoreSSL) if err != nil { return 0, fmt.Errorf("上传证书失败: %v", err) } data, ok := resp["data"].(map[string]any) if !ok { return 0, fmt.Errorf("上传证书响应格式错误: %v", resp) } id, ok := data["id"].(float64) if !ok { return 0, fmt.Errorf("上传证书响应中未找到 id: %v", resp) } return int(id), nil } // GetDomainIdMapFromSite 获取指定站点的域名列表,并返回域名到 ID 的映射 func (l *LecdnConfig) GetDomainIdMapFromSite(siteId int) (map[string]int, error) { url := fmt.Sprintf("%s/prod-api/site/%d/domain_name", l.BaseURL, siteId) resp, err := requestLecdn(url, "GET", l.Token, nil, l.IgnoreSSL) if err != nil { return nil, fmt.Errorf("获取域名列表失败: %v", err) } data, ok := resp["data"].([]any) if !ok { return nil, fmt.Errorf("获取域名列表响应格式错误: %v", resp) } domains := make(map[string]int) for _, domain := range data { domainData, ok := domain.(map[string]any) if !ok { return nil, fmt.Errorf("域名列表项格式错误: %v", domain) } domainName, ok := domainData["domain_name"].(string) if !ok { return nil, fmt.Errorf("域名列表项中未找到 domain_name: %v", domainData) } id, ok := domainData["id"].(float64) if !ok { return nil, fmt.Errorf("域名列表项中未找到 id: %v", domainData) } domains[domainName] = int(id) } return domains, nil } // DeployCert 部署证书到指定站点的域名 func (l *LecdnConfig) DeployCert(siteId, domainId, certId int) error { url := fmt.Sprintf("%s/prod-api/site/%d/domain_name/certificate", l.BaseURL, siteId) params := map[string]any{"certificate_enable": true, "certificate_id": certId, "domain_name_id": domainId} _, err := requestLecdn(url, "PUT", l.Token, params, l.IgnoreSSL) if err != nil { return fmt.Errorf("部署证书失败: %v", err) } return nil } // DeployLeCDN 部署 LeCDN 证书主方法 func DeployLeCDN(cfg map[string]any) error { cert, ok := cfg["certificate"].(map[string]any) if !ok { return fmt.Errorf("证书不存在") } certPem, ok := cert["cert"].(string) if !ok { return fmt.Errorf("证书错误:cert") } keyPem, ok := cert["key"].(string) if !ok { return fmt.Errorf("证书错误:key") } certName, err := public.GetSHA256(certPem) if err != nil { return fmt.Errorf("获取证书名称失败: %v", err) } certName = "ALLinSSL-" + certName 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") } domainName, ok := cfg["domain"].(string) if !ok || domainName == "" { return fmt.Errorf("参数错误:domain") } var siteId int switch v := cfg["site_id"].(type) { case float64: siteId = int(v) case string: siteId, err = strconv.Atoi(v) if err != nil { return fmt.Errorf("参数错误:site_id") } case int: siteId = v default: return fmt.Errorf("参数错误:site_id") } l, err := NewLecdnConfig(providerID) if err != nil { return err } err = l.client() if err != nil { return fmt.Errorf("登录 LeCDN 失败: %v", err) } certId := 0 // 获取证书列表 certList, err := l.GetCertList() if err == nil && len(certList) > 0 { for _, c := range certList { name, ok := c["name"].(string) if !ok { continue } id, ok := c["id"].(float64) if !ok { continue } if name == certName { certId = int(id) } } } if certId == 0 { // 上传证书 certId, err = l.UploadCert(certPem, keyPem, certName) if err != nil { return fmt.Errorf("上传证书失败: %v", err) } } // 获取域名列表 domainList, err := l.GetDomainIdMapFromSite(siteId) if err != nil { return fmt.Errorf("获取域名列表失败: %v", err) } domainId, ok := domainList[domainName] if !ok { return fmt.Errorf("未找到域名 %s 的 ID", domainName) } // 部署证书到域名 err = l.DeployCert(siteId, domainId, certId) if err != nil { return fmt.Errorf("部署证书到域名失败: %v", err) } // 部署完成,返回成功 return nil }