Skip to content

Commit c5167b3

Browse files
author
zhangchenhao
committed
插件简单源码示例(多吉云)
1 parent aca870a commit c5167b3

File tree

2 files changed

+331
-0
lines changed

2 files changed

+331
-0
lines changed

plugins/doge/action.go

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package main
2+
3+
import (
4+
"crypto/hmac"
5+
"crypto/sha1"
6+
"encoding/hex"
7+
"encoding/json"
8+
"fmt"
9+
"io"
10+
"net/http"
11+
"net/url"
12+
"strings"
13+
)
14+
15+
type Auth struct {
16+
AccessKey string `json:"access_key"`
17+
SecretKey string `json:"secret_key"`
18+
}
19+
20+
func NewAuth(accessKey, secretKey string) *Auth {
21+
return &Auth{
22+
AccessKey: accessKey,
23+
SecretKey: secretKey,
24+
}
25+
}
26+
27+
func Cdn(cfg map[string]any) (*Response, error) {
28+
if cfg == nil {
29+
return nil, fmt.Errorf("config cannot be nil")
30+
}
31+
certStr, ok := cfg["cert"].(string)
32+
if !ok || certStr == "" {
33+
return nil, fmt.Errorf("cert is required and must be a string")
34+
}
35+
keyStr, ok := cfg["key"].(string)
36+
if !ok || keyStr == "" {
37+
return nil, fmt.Errorf("key is required and must be a string")
38+
}
39+
accessKey, ok := cfg["access_key"].(string)
40+
if !ok || accessKey == "" {
41+
return nil, fmt.Errorf("access_key is required and must be a string")
42+
}
43+
secretKey, ok := cfg["secret_key"].(string)
44+
if !ok || secretKey == "" {
45+
return nil, fmt.Errorf("secret_key is required and must be a string")
46+
}
47+
domain, ok := cfg["domain"].(string)
48+
if !ok || domain == "" {
49+
return nil, fmt.Errorf("domain is required and must be a string")
50+
}
51+
sha256, err := GetSHA256(certStr)
52+
if err != nil {
53+
return nil, fmt.Errorf("failed to get SHA256 of cert: %w", err)
54+
}
55+
note := fmt.Sprintf("allinssl-%s", sha256)
56+
57+
a := NewAuth(accessKey, secretKey)
58+
// 检查证书是否已存在于 CDN
59+
// 只根据证书名称检查是否存在,格式为 "allinssl-<sha256>"
60+
certList, err := a.listCertFromCdn()
61+
if err != nil {
62+
return nil, fmt.Errorf("failed to list certs from CDN: %w", err)
63+
}
64+
var certID float64
65+
for _, cert := range certList {
66+
if cert["note"] == note {
67+
certID, ok = cert["id"].(float64)
68+
if !ok {
69+
certID = 0
70+
}
71+
}
72+
}
73+
// 如果证书不存在,则上传证书到 CDN
74+
if certID == 0 {
75+
certID, err = a.uploadCertToCdn(certStr, keyStr, note)
76+
if err != nil || certID == 0 {
77+
return nil, fmt.Errorf("failed to upload to CDN: %w", err)
78+
}
79+
}
80+
// 绑定证书到域名
81+
bindRes, err := a.bindCertToCdn(certID, domain)
82+
if err != nil {
83+
return nil, fmt.Errorf("failed to bind cert to CDN: %w", err)
84+
}
85+
86+
return &Response{
87+
Status: "success",
88+
Message: "Certificate uploaded and bound successfully",
89+
Result: bindRes,
90+
}, nil
91+
}
92+
93+
func (a Auth) uploadCertToCdn(cert, key, note string) (float64, error) {
94+
params := map[string]any{
95+
"cert": cert,
96+
"private": key,
97+
"note": note,
98+
}
99+
100+
res, err := a.DogeCloudAPI("/cdn/cert/upload.json", params, true)
101+
if err != nil {
102+
return 0, fmt.Errorf("failed to call DogeCloud API: %w", err)
103+
}
104+
code, ok := res["code"].(float64)
105+
if !ok {
106+
return 0, fmt.Errorf("invalid response format: code not found")
107+
}
108+
if code != 200 {
109+
return 0, fmt.Errorf("DogeCloud API error: %s", res["msg"])
110+
}
111+
data, ok := res["data"].(map[string]any)
112+
if !ok {
113+
return 0, fmt.Errorf("invalid response format: data not found")
114+
}
115+
certID, ok := data["id"].(float64)
116+
if !ok {
117+
return 0, fmt.Errorf("invalid response format: id not found")
118+
}
119+
return certID, nil
120+
}
121+
122+
func (a Auth) listCertFromCdn() ([]map[string]any, error) {
123+
res, err := a.DogeCloudAPI("/cdn/cert/list.json", map[string]interface{}{}, true)
124+
if err != nil {
125+
return nil, fmt.Errorf("failed to call DogeCloud API: %w", err)
126+
}
127+
code, ok := res["code"].(float64)
128+
if !ok {
129+
return nil, fmt.Errorf("invalid response format: code not found")
130+
}
131+
if code != 200 {
132+
return nil, fmt.Errorf("DogeCloud API error: %s", res["msg"])
133+
}
134+
data, ok := res["data"].(map[string]any)
135+
if !ok {
136+
return nil, fmt.Errorf("invalid response format: data not found")
137+
}
138+
certList, ok := data["certs"].([]any)
139+
if !ok {
140+
return nil, fmt.Errorf("invalid response format: certs not found")
141+
}
142+
certs := make([]map[string]any, 0, len(certList))
143+
for _, cert := range certList {
144+
certMap, ok := cert.(map[string]any)
145+
if !ok {
146+
return nil, fmt.Errorf("invalid response format: cert item is not a map")
147+
}
148+
certs = append(certs, certMap)
149+
}
150+
return certs, nil
151+
}
152+
153+
func (a Auth) bindCertToCdn(certID float64, domain string) (map[string]interface{}, error) {
154+
params := map[string]interface{}{
155+
"id": certID,
156+
"domain": domain,
157+
}
158+
res, err := a.DogeCloudAPI("/cdn/cert/bind.json", params, true)
159+
if err != nil {
160+
return nil, fmt.Errorf("failed to call DogeCloud API: %w", err)
161+
}
162+
code, ok := res["code"].(float64)
163+
if !ok {
164+
return nil, fmt.Errorf("invalid response format: code not found")
165+
}
166+
if code != 200 {
167+
return nil, fmt.Errorf("DogeCloud API error: %s", res["msg"])
168+
}
169+
return res, nil
170+
171+
}
172+
173+
// DogeCloudAPI 调用多吉云的 API 根据多吉云官网示例修改
174+
func (a Auth) DogeCloudAPI(apiPath string, data map[string]interface{}, jsonMode bool) (map[string]interface{}, error) {
175+
AccessKey := a.AccessKey
176+
SecretKey := a.SecretKey
177+
178+
body := ""
179+
mime := ""
180+
if jsonMode {
181+
_body, err := json.Marshal(data)
182+
if err != nil {
183+
return nil, err
184+
}
185+
body = string(_body)
186+
mime = "application/json"
187+
} else {
188+
values := url.Values{}
189+
for k, v := range data {
190+
values.Set(k, v.(string))
191+
}
192+
body = values.Encode()
193+
mime = "application/x-www-form-urlencoded"
194+
}
195+
196+
signStr := apiPath + "\n" + body
197+
hmacObj := hmac.New(sha1.New, []byte(SecretKey))
198+
hmacObj.Write([]byte(signStr))
199+
sign := hex.EncodeToString(hmacObj.Sum(nil))
200+
Authorization := "TOKEN " + AccessKey + ":" + sign
201+
202+
req, err := http.NewRequest("POST", "https://api.dogecloud.com"+apiPath, strings.NewReader(body))
203+
if err != nil {
204+
return nil, err // 创建请求错误
205+
}
206+
req.Header.Add("Content-Type", mime)
207+
req.Header.Add("Authorization", Authorization)
208+
client := http.Client{}
209+
resp, err := client.Do(req)
210+
if err != nil {
211+
return nil, err
212+
} // 网络错误
213+
defer resp.Body.Close()
214+
r, err := io.ReadAll(resp.Body)
215+
if err != nil {
216+
return nil, err // 读取响应错误
217+
}
218+
var result map[string]interface{}
219+
220+
err = json.Unmarshal(r, &result)
221+
if err != nil {
222+
return nil, err
223+
}
224+
return result, nil
225+
}

plugins/doge/main.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package main
2+
3+
import (
4+
"crypto/sha256"
5+
"crypto/x509"
6+
"encoding/hex"
7+
"encoding/json"
8+
"encoding/pem"
9+
"fmt"
10+
"io"
11+
"os"
12+
)
13+
14+
type ActionInfo struct {
15+
Name string `json:"name"`
16+
Description string `json:"description"`
17+
}
18+
19+
type Request struct {
20+
Action string `json:"action"`
21+
Params map[string]interface{} `json:"params"`
22+
}
23+
24+
type Response struct {
25+
Status string `json:"status"`
26+
Message string `json:"message"`
27+
Result map[string]interface{} `json:"result"`
28+
}
29+
30+
var pluginMeta = map[string]interface{}{
31+
"name": "doge",
32+
"description": "部署到多吉云",
33+
"version": "1.0.0",
34+
"author": "主包",
35+
"actions": []ActionInfo{
36+
{Name: "cdn", Description: "部署到多吉云cdn"},
37+
},
38+
}
39+
40+
func GetSHA256(certStr string) (string, error) {
41+
certPEM := []byte(certStr)
42+
block, _ := pem.Decode(certPEM)
43+
if block == nil {
44+
return "", fmt.Errorf("无法解析证书 PEM")
45+
}
46+
cert, err := x509.ParseCertificate(block.Bytes)
47+
if err != nil {
48+
return "", fmt.Errorf("解析证书失败: %v", err)
49+
}
50+
51+
sha256Hash := sha256.Sum256(cert.Raw)
52+
return hex.EncodeToString(sha256Hash[:]), nil
53+
}
54+
55+
func outputJSON(resp *Response) {
56+
_ = json.NewEncoder(os.Stdout).Encode(resp)
57+
}
58+
59+
func outputError(msg string, err error) {
60+
outputJSON(&Response{
61+
Status: "error",
62+
Message: fmt.Sprintf("%s: %v", msg, err),
63+
})
64+
}
65+
66+
func main() {
67+
var req Request
68+
input, err := io.ReadAll(os.Stdin)
69+
if err != nil {
70+
outputError("读取输入失败", err)
71+
return
72+
}
73+
74+
if err := json.Unmarshal(input, &req); err != nil {
75+
outputError("解析请求失败", err)
76+
return
77+
}
78+
79+
switch req.Action {
80+
case "get_metadata":
81+
outputJSON(&Response{
82+
Status: "success",
83+
Message: "插件信息",
84+
Result: pluginMeta,
85+
})
86+
case "list_actions":
87+
outputJSON(&Response{
88+
Status: "success",
89+
Message: "支持的动作",
90+
Result: map[string]interface{}{"actions": pluginMeta["actions"]},
91+
})
92+
case "cdn":
93+
rep, err := Cdn(req.Params)
94+
if err != nil {
95+
outputError("CDN 部署失败", err)
96+
return
97+
}
98+
outputJSON(rep)
99+
100+
default:
101+
outputJSON(&Response{
102+
Status: "error",
103+
Message: "未知 action: " + req.Action,
104+
})
105+
}
106+
}

0 commit comments

Comments
 (0)