Files
tyapi-server/internal/infrastructure/task/handlers/article_task_handler.go
2025-09-12 01:15:09 +08:00

304 lines
9.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}