Files
OpenFlare/internal/apps/openflare/tls/tasks.go
T
ryan 3a2878d070 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.
2026-06-22 10:54:15 +08:00

101 lines
3.0 KiB
Go

// 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
}