Scert/backend/internal/httpapi/task_api.go

184 lines
5.8 KiB
Go

package httpapi
import (
"context"
"database/sql"
"errors"
"net/http"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"git.pcux.cn/xuans/Scert/backend/internal/domain"
"git.pcux.cn/xuans/Scert/backend/internal/repository"
)
func (s *Server) issueDomainAsTask(c *gin.Context) {
if s.issueCertificate == nil {
s.writeError(c, http.StatusNotImplemented, "证书申请接口尚未配置", errors.New("证书申请工作流未配置"))
return
}
domainID := strings.TrimSpace(c.Param("id"))
if _, err := s.repository.GetByID(c.Request.Context(), domainID); err != nil {
if repository.IsNotFound(err) {
s.writeError(c, http.StatusNotFound, "域名不存在", err)
return
}
s.writeError(c, http.StatusInternalServerError, "读取域名失败", err)
return
}
if active, err := s.tasks.FindActiveTaskByDomain(c.Request.Context(), domainID, "certificate_issue"); err == nil {
c.JSON(http.StatusConflict, gin.H{"error": "该域名已有正在执行的证书任务", "task": active})
return
} else if !errors.Is(err, sql.ErrNoRows) {
s.writeError(c, http.StatusInternalServerError, "检查正在执行的任务失败", err)
return
}
now := time.Now().UTC()
task := repository.Task{
ID: domain.NewID(),
DomainID: domainID,
TaskType: "certificate_issue",
Status: "queued",
Stage: "queued",
Message: "等待执行证书申请",
CreatedAt: now,
UpdatedAt: now,
}
if err := s.tasks.CreateTask(c.Request.Context(), task); err != nil {
s.writeError(c, http.StatusInternalServerError, "创建证书任务失败", err)
return
}
attempt := repository.TaskAttempt{
ID: domain.NewID(),
TaskID: task.ID,
AttemptNumber: 1,
Stage: "certificate_issue",
Status: "running",
Message: "开始申请证书",
StartedAt: &now,
CreatedAt: now,
}
if err := s.tasks.CreateAttempt(c.Request.Context(), attempt); err != nil {
_ = s.tasks.UpdateTask(context.Background(), task.ID, "failed", "certificate_issue", "创建任务尝试失败", &now, &now)
s.writeError(c, http.StatusInternalServerError, "创建任务尝试失败", err)
return
}
if err := s.tasks.UpdateTask(c.Request.Context(), task.ID, "running", "certificate_issue", "正在申请证书并部署", &now, nil); err != nil {
s.writeError(c, http.StatusInternalServerError, "更新任务状态失败", err)
return
}
certificateResult, deploymentReport, issueErr := s.issueCertificate(c.Request.Context(), domainID)
finished := time.Now().UTC()
status := "success"
message := "证书申请和部署完成"
if issueErr != nil {
status = "failed"
message = issueErr.Error()
}
_ = s.tasks.UpdateTask(context.Background(), task.ID, status, "certificate_issue", message, &now, &finished)
_ = s.tasks.FinishAttempt(context.Background(), attempt.ID, status, message, &finished)
task.Status = status
task.Stage = "certificate_issue"
task.Message = message
task.StartedAt = &now
task.FinishedAt = &finished
task.UpdatedAt = finished
if s.audit != nil {
auditStatus := status
if issueErr != nil {
auditStatus = "error"
}
_ = s.audit.CreateAuditLog(context.Background(), repository.AuditLog{
Action: "certificate_issue",
ResourceType: "domain",
ResourceID: domainID,
Status: auditStatus,
Message: message,
})
}
response := gin.H{
"task": task,
"certificate": certificateResult,
"deployment": deploymentReport,
"task_status": status,
"finished_at": finished,
}
if issueErr != nil {
response["error"] = issueErr.Error()
c.JSON(http.StatusBadGateway, response)
return
}
response["task"] = task
c.JSON(http.StatusOK, response)
}
func (s *Server) listTasks(c *gin.Context) {
if s.tasks == nil {
s.writeError(c, http.StatusNotImplemented, "任务管理接口尚未配置", errors.New("任务仓储未配置"))
return
}
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
items, err := s.tasks.ListTasks(c.Request.Context(), c.Query("domain_id"), limit)
if err != nil {
s.writeError(c, http.StatusInternalServerError, "查询任务失败", err)
return
}
c.JSON(http.StatusOK, gin.H{"items": items})
}
func (s *Server) getTask(c *gin.Context) {
if s.tasks == nil {
s.writeError(c, http.StatusNotImplemented, "任务管理接口尚未配置", errors.New("任务仓储未配置"))
return
}
item, err := s.tasks.GetTask(c.Request.Context(), c.Param("id"))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
s.writeError(c, http.StatusNotFound, "任务不存在", err)
return
}
s.writeError(c, http.StatusInternalServerError, "读取任务失败", err)
return
}
c.JSON(http.StatusOK, item)
}
func (s *Server) listTaskAttempts(c *gin.Context) {
if s.tasks == nil {
s.writeError(c, http.StatusNotImplemented, "任务管理接口尚未配置", errors.New("任务仓储未配置"))
return
}
if _, err := s.tasks.GetTask(c.Request.Context(), c.Param("id")); err != nil {
if errors.Is(err, sql.ErrNoRows) {
s.writeError(c, http.StatusNotFound, "任务不存在", err)
return
}
s.writeError(c, http.StatusInternalServerError, "读取任务失败", err)
return
}
items, err := s.tasks.ListAttempts(c.Request.Context(), c.Param("id"))
if err != nil {
s.writeError(c, http.StatusInternalServerError, "查询任务尝试失败", err)
return
}
c.JSON(http.StatusOK, gin.H{"items": items})
}
func (s *Server) listAuditLogs(c *gin.Context) {
reader, ok := s.audit.(repository.AuditReader)
if !ok {
s.writeError(c, http.StatusNotImplemented, "审计日志查询接口尚未配置", errors.New("审计日志读取服务未配置"))
return
}
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
items, err := reader.ListAuditLogs(c.Request.Context(), limit)
if err != nil {
s.writeError(c, http.StatusInternalServerError, "查询审计日志失败", err)
return
}
c.JSON(http.StatusOK, gin.H{"items": items})
}