feat(api): integrate TLS certificate renewal into async task framework

Replace native goroutines in RenewCertificate logic with Asynq task dispatching to support queue execution, retry capability, and detailed task execution logs.
This commit is contained in:
ryan
2026-06-22 10:54:15 +08:00
parent df3bcd3d19
commit 3a2878d070
10 changed files with 303 additions and 4 deletions
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package tls
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/task"
)
const (
// SSLSingleRenewTask renews a single ACME TLS certificate.
SSLSingleRenewTask = "openflare:ssl_single_renew"
// TaskTypeSSLSingleRenew is the admin task type for single SSL renewal.
TaskTypeSSLSingleRenew = "of_ssl_single_renew"
)
// SSLSingleRenewMeta describes the single SSL renewal task.
var SSLSingleRenewMeta = task.TaskMeta{
Type: TaskTypeSSLSingleRenew,
AsynqTask: SSLSingleRenewTask,
Name: "OpenFlare 单证书 SSL 续期",
Description: "对单个指定的 ACME 证书执行续期",
SupportsTime: false,
MaxRetry: task.DefaultMaxRetry,
Queue: task.QueueDefault,
Retryable: true,
Params: []task.TaskParam{
{
Name: "id",
Label: "证书 ID",
Type: "number",
Required: true,
Placeholder: "请输入证书 ID",
Description: "待续期的 TLS 证书 ID",
},
},
}
// SSLSingleRenewPayload is the payload structure for SSLSingleRenewTask.
type SSLSingleRenewPayload struct {
ID uint `json:"id"`
}
// SSLSingleRenewHandler renews a specific TLS certificate.
type SSLSingleRenewHandler struct{}
// ValidatePayload validates and normalizes the task payload.
func (h *SSLSingleRenewHandler) ValidatePayload(payload []byte) ([]byte, error) {
if len(payload) == 0 {
return nil, errors.New("任务参数不能为空")
}
var req SSLSingleRenewPayload
if err := json.Unmarshal(payload, &req); err != nil {
return nil, fmt.Errorf("无效的 JSON 格式: %w", err)
}
if req.ID == 0 {
return nil, errors.New("证书 ID 不能为空或零")
}
return json.Marshal(req)
}
// Execute runs the certificate renewal for the specified ID.
func (h *SSLSingleRenewHandler) Execute(ctx context.Context, payload []byte) (*task.TaskResult, error) {
var req SSLSingleRenewPayload
if err := json.Unmarshal(payload, &req); err != nil {
return nil, fmt.Errorf("解析任务参数: %w", err)
}
task.AppendLog(ctx, "开始续期证书,ID: %d", req.ID)
cert, err := model.GetTLSCertificateByID(ctx, req.ID)
if err != nil {
task.AppendLog(ctx, "获取证书记录失败 ID=%d: %v", req.ID, err)
return nil, fmt.Errorf("获取证书记录失败: %w", err)
}
if cert.Provider != tlsProviderACME {
task.AppendLog(ctx, "证书 %s (ID=%d) 不是 ACME 托管证书,无法自动续期 (Provider: %s)", cert.PrimaryDomain, req.ID, cert.Provider)
return nil, fmt.Errorf("证书 %s 不是 ACME 托管证书,无法自动续期", cert.PrimaryDomain)
}
task.AppendLog(ctx, "准备为域名 [%s] 申请/续期证书", cert.PrimaryDomain)
if err := obtainTLSCertificate(ctx, cert); err != nil {
task.AppendLog(ctx, "申请证书失败: %v", err)
return nil, err
}
msg := fmt.Sprintf("证书 %s 续签成功", cert.PrimaryDomain)
task.AppendLog(ctx, "%s", msg)
return &task.TaskResult{Message: msg}, nil
}