This commit is contained in:
2025-09-12 01:15:09 +08:00
parent c563b2266b
commit e05ad9e223
103 changed files with 20034 additions and 1041 deletions

View File

@@ -0,0 +1,304 @@
package handlers
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/hibiken/asynq"
"go.uber.org/zap"
"tyapi-server/internal/application/article"
"tyapi-server/internal/infrastructure/task/entities"
"tyapi-server/internal/infrastructure/task/repositories"
"tyapi-server/internal/infrastructure/task/types"
)
// ArticleTaskHandler 文章任务处理器
type ArticleTaskHandler struct {
logger *zap.Logger
articleApplicationService article.ArticleApplicationService
asyncTaskRepo repositories.AsyncTaskRepository
}
// NewArticleTaskHandler 创建文章任务处理器
func NewArticleTaskHandler(logger *zap.Logger, articleApplicationService article.ArticleApplicationService, asyncTaskRepo repositories.AsyncTaskRepository) *ArticleTaskHandler {
return &ArticleTaskHandler{
logger: logger,
articleApplicationService: articleApplicationService,
asyncTaskRepo: asyncTaskRepo,
}
}
// HandleArticlePublish 处理文章发布任务
func (h *ArticleTaskHandler) HandleArticlePublish(ctx context.Context, t *asynq.Task) error {
h.logger.Info("开始处理文章发布任务")
var payload ArticlePublishPayload
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
h.logger.Error("解析文章发布任务载荷失败", zap.Error(err))
h.updateTaskStatus(ctx, t, "failed", "解析任务载荷失败")
return err
}
h.logger.Info("处理文章发布任务",
zap.String("article_id", payload.ArticleID),
zap.Time("publish_at", payload.PublishAt))
// 检查任务是否已被取消
if err := h.checkTaskStatus(ctx, t); err != nil {
h.logger.Info("任务已被取消,跳过执行", zap.String("article_id", payload.ArticleID))
return nil // 静默返回,不报错
}
// 调用文章应用服务发布文章
if h.articleApplicationService != nil {
err := h.articleApplicationService.PublishArticleByID(ctx, payload.ArticleID)
if err != nil {
h.logger.Error("文章发布失败", zap.String("article_id", payload.ArticleID), zap.Error(err))
h.updateTaskStatus(ctx, t, "failed", "文章发布失败: "+err.Error())
return err
}
} else {
h.logger.Warn("文章应用服务未初始化,跳过发布", zap.String("article_id", payload.ArticleID))
h.updateTaskStatus(ctx, t, "failed", "文章应用服务未初始化")
return nil
}
// 更新任务状态为成功
h.updateTaskStatus(ctx, t, "completed", "")
h.logger.Info("文章发布任务处理完成", zap.String("article_id", payload.ArticleID))
return nil
}
// HandleArticleCancel 处理文章取消任务
func (h *ArticleTaskHandler) HandleArticleCancel(ctx context.Context, t *asynq.Task) error {
h.logger.Info("开始处理文章取消任务")
var payload ArticleCancelPayload
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
h.logger.Error("解析文章取消任务载荷失败", zap.Error(err))
return err
}
h.logger.Info("处理文章取消任务", zap.String("article_id", payload.ArticleID))
// 这里实现文章取消的具体逻辑
// 例如:更新文章状态、取消定时发布等
h.logger.Info("文章取消任务处理完成", zap.String("article_id", payload.ArticleID))
return nil
}
// HandleArticleModify 处理文章修改任务
func (h *ArticleTaskHandler) HandleArticleModify(ctx context.Context, t *asynq.Task) error {
h.logger.Info("开始处理文章修改任务")
var payload ArticleModifyPayload
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
h.logger.Error("解析文章修改任务载荷失败", zap.Error(err))
return err
}
h.logger.Info("处理文章修改任务",
zap.String("article_id", payload.ArticleID),
zap.Time("new_publish_at", payload.NewPublishAt))
// 这里实现文章修改的具体逻辑
// 例如:更新文章发布时间、重新调度任务等
h.logger.Info("文章修改任务处理完成", zap.String("article_id", payload.ArticleID))
return nil
}
// ArticlePublishPayload 文章发布任务载荷
type ArticlePublishPayload struct {
ArticleID string `json:"article_id"`
PublishAt time.Time `json:"publish_at"`
UserID string `json:"user_id"`
}
// GetType 获取任务类型
func (p *ArticlePublishPayload) GetType() types.TaskType {
return types.TaskTypeArticlePublish
}
// ToJSON 序列化为JSON
func (p *ArticlePublishPayload) ToJSON() ([]byte, error) {
return json.Marshal(p)
}
// FromJSON 从JSON反序列化
func (p *ArticlePublishPayload) FromJSON(data []byte) error {
return json.Unmarshal(data, p)
}
// ArticleCancelPayload 文章取消任务载荷
type ArticleCancelPayload struct {
ArticleID string `json:"article_id"`
UserID string `json:"user_id"`
}
// GetType 获取任务类型
func (p *ArticleCancelPayload) GetType() types.TaskType {
return types.TaskTypeArticleCancel
}
// ToJSON 序列化为JSON
func (p *ArticleCancelPayload) ToJSON() ([]byte, error) {
return json.Marshal(p)
}
// FromJSON 从JSON反序列化
func (p *ArticleCancelPayload) FromJSON(data []byte) error {
return json.Unmarshal(data, p)
}
// ArticleModifyPayload 文章修改任务载荷
type ArticleModifyPayload struct {
ArticleID string `json:"article_id"`
NewPublishAt time.Time `json:"new_publish_at"`
UserID string `json:"user_id"`
}
// GetType 获取任务类型
func (p *ArticleModifyPayload) GetType() types.TaskType {
return types.TaskTypeArticleModify
}
// ToJSON 序列化为JSON
func (p *ArticleModifyPayload) ToJSON() ([]byte, error) {
return json.Marshal(p)
}
// FromJSON 从JSON反序列化
func (p *ArticleModifyPayload) FromJSON(data []byte) error {
return json.Unmarshal(data, p)
}
// updateTaskStatus 更新任务状态
func (h *ArticleTaskHandler) updateTaskStatus(ctx context.Context, t *asynq.Task, status string, errorMsg string) {
// 从任务载荷中提取任务ID
var payload map[string]interface{}
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
h.logger.Error("解析任务载荷失败,无法更新状态", zap.Error(err))
return
}
// 尝试从payload中获取任务ID
taskID, ok := payload["task_id"].(string)
if !ok {
// 如果没有task_id尝试从article_id生成
if articleID, ok := payload["article_id"].(string); ok {
taskID = fmt.Sprintf("article-publish-%s", articleID)
} else {
h.logger.Error("无法从任务载荷中获取任务ID")
return
}
}
// 根据状态决定更新方式
if status == "failed" {
// 失败时:需要检查是否达到最大重试次数
h.handleTaskFailure(ctx, taskID, errorMsg)
} else if status == "completed" {
// 成功时:清除错误信息并更新状态
if err := h.asyncTaskRepo.UpdateStatusWithSuccess(ctx, taskID, entities.TaskStatus(status)); err != nil {
h.logger.Error("更新任务状态失败",
zap.String("task_id", taskID),
zap.String("status", status),
zap.Error(err))
}
} else {
// 其他状态:只更新状态
if err := h.asyncTaskRepo.UpdateStatus(ctx, taskID, entities.TaskStatus(status)); err != nil {
h.logger.Error("更新任务状态失败",
zap.String("task_id", taskID),
zap.String("status", status),
zap.Error(err))
}
}
h.logger.Info("任务状态已更新",
zap.String("task_id", taskID),
zap.String("status", status),
zap.String("error_msg", errorMsg))
}
// handleTaskFailure 处理任务失败
func (h *ArticleTaskHandler) handleTaskFailure(ctx context.Context, taskID string, errorMsg string) {
// 获取当前任务信息
task, err := h.asyncTaskRepo.GetByID(ctx, taskID)
if err != nil {
h.logger.Error("获取任务信息失败", zap.String("task_id", taskID), zap.Error(err))
return
}
// 增加重试次数
newRetryCount := task.RetryCount + 1
// 检查是否达到最大重试次数
if newRetryCount >= task.MaxRetries {
// 达到最大重试次数,标记为最终失败
if err := h.asyncTaskRepo.UpdateStatusWithRetryAndError(ctx, taskID, entities.TaskStatusFailed, errorMsg); err != nil {
h.logger.Error("更新任务状态失败",
zap.String("task_id", taskID),
zap.String("status", "failed"),
zap.Error(err))
}
h.logger.Info("任务最终失败,已达到最大重试次数",
zap.String("task_id", taskID),
zap.Int("retry_count", newRetryCount),
zap.Int("max_retries", task.MaxRetries))
} else {
// 未达到最大重试次数保持pending状态记录错误信息
if err := h.asyncTaskRepo.UpdateRetryCountAndError(ctx, taskID, newRetryCount, errorMsg); err != nil {
h.logger.Error("更新任务重试次数失败",
zap.String("task_id", taskID),
zap.Int("retry_count", newRetryCount),
zap.Error(err))
}
h.logger.Info("任务失败,准备重试",
zap.String("task_id", taskID),
zap.Int("retry_count", newRetryCount),
zap.Int("max_retries", task.MaxRetries))
}
}
// checkTaskStatus 检查任务状态
func (h *ArticleTaskHandler) checkTaskStatus(ctx context.Context, t *asynq.Task) error {
// 从任务载荷中提取任务ID
var payload map[string]interface{}
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
h.logger.Error("解析任务载荷失败,无法检查状态", zap.Error(err))
return err
}
// 尝试从payload中获取任务ID
taskID, ok := payload["task_id"].(string)
if !ok {
// 如果没有task_id尝试从article_id生成
if articleID, ok := payload["article_id"].(string); ok {
taskID = fmt.Sprintf("article-publish-%s", articleID)
} else {
h.logger.Error("无法从任务载荷中获取任务ID")
return fmt.Errorf("无法获取任务ID")
}
}
// 查询任务状态
task, err := h.asyncTaskRepo.GetByID(ctx, taskID)
if err != nil {
h.logger.Error("查询任务状态失败", zap.String("task_id", taskID), zap.Error(err))
return err
}
// 检查任务是否已被取消
if task.Status == entities.TaskStatusCancelled {
h.logger.Info("任务已被取消", zap.String("task_id", taskID))
return fmt.Errorf("任务已被取消")
}
return nil
}