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 }