| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | package article | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"tyapi-server/internal/application/article/dto/commands" | 
					
						
							|  |  |  | 	appQueries "tyapi-server/internal/application/article/dto/queries" | 
					
						
							|  |  |  | 	"tyapi-server/internal/application/article/dto/responses" | 
					
						
							|  |  |  | 	"tyapi-server/internal/domains/article/entities" | 
					
						
							|  |  |  | 	"tyapi-server/internal/domains/article/repositories" | 
					
						
							|  |  |  | 	repoQueries "tyapi-server/internal/domains/article/repositories/queries" | 
					
						
							|  |  |  | 	"tyapi-server/internal/domains/article/services" | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 	task_entities "tyapi-server/internal/infrastructure/task/entities" | 
					
						
							|  |  |  | 	task_interfaces "tyapi-server/internal/infrastructure/task/interfaces" | 
					
						
							|  |  |  | 	shared_interfaces "tyapi-server/internal/shared/interfaces" | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"go.uber.org/zap" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ArticleApplicationServiceImpl 文章应用服务实现 | 
					
						
							|  |  |  | type ArticleApplicationServiceImpl struct { | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 	articleRepo      repositories.ArticleRepository | 
					
						
							|  |  |  | 	categoryRepo     repositories.CategoryRepository | 
					
						
							|  |  |  | 	tagRepo          repositories.TagRepository | 
					
						
							|  |  |  | 	articleService   *services.ArticleService | 
					
						
							|  |  |  | 	taskManager      task_interfaces.TaskManager | 
					
						
							|  |  |  | 	logger           *zap.Logger | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewArticleApplicationService 创建文章应用服务 | 
					
						
							|  |  |  | func NewArticleApplicationService( | 
					
						
							|  |  |  | 	articleRepo repositories.ArticleRepository, | 
					
						
							|  |  |  | 	categoryRepo repositories.CategoryRepository, | 
					
						
							|  |  |  | 	tagRepo repositories.TagRepository, | 
					
						
							|  |  |  | 	articleService *services.ArticleService, | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 	taskManager task_interfaces.TaskManager, | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	logger *zap.Logger, | 
					
						
							|  |  |  | ) ArticleApplicationService { | 
					
						
							|  |  |  | 	return &ArticleApplicationServiceImpl{ | 
					
						
							|  |  |  | 		articleRepo:    articleRepo, | 
					
						
							|  |  |  | 		categoryRepo:   categoryRepo, | 
					
						
							|  |  |  | 		tagRepo:        tagRepo, | 
					
						
							|  |  |  | 		articleService: articleService, | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 		taskManager:    taskManager, | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 		logger:         logger, | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CreateArticle 创建文章 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) CreateArticle(ctx context.Context, cmd *commands.CreateArticleCommand) error { | 
					
						
							|  |  |  | 	// 1. 参数验证 | 
					
						
							|  |  |  | 	if err := s.validateCreateArticle(cmd); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("参数验证失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 创建文章实体 | 
					
						
							|  |  |  | 	article := &entities.Article{ | 
					
						
							|  |  |  | 		Title:      cmd.Title, | 
					
						
							|  |  |  | 		Content:    cmd.Content, | 
					
						
							|  |  |  | 		Summary:    cmd.Summary, | 
					
						
							|  |  |  | 		CoverImage: cmd.CoverImage, | 
					
						
							|  |  |  | 		CategoryID: cmd.CategoryID, | 
					
						
							|  |  |  | 		IsFeatured: cmd.IsFeatured, | 
					
						
							|  |  |  | 		Status:     entities.ArticleStatusDraft, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 3. 调用领域服务验证 | 
					
						
							|  |  |  | 	if err := s.articleService.ValidateArticle(article); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("业务验证失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 4. 保存文章 | 
					
						
							|  |  |  | 	_, err := s.articleRepo.Create(ctx, *article) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("创建文章失败", zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("创建文章失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 5. 处理标签关联 | 
					
						
							|  |  |  | 	if len(cmd.TagIDs) > 0 { | 
					
						
							|  |  |  | 		for _, tagID := range cmd.TagIDs { | 
					
						
							|  |  |  | 			if err := s.tagRepo.AddTagToArticle(ctx, article.ID, tagID); err != nil { | 
					
						
							|  |  |  | 				s.logger.Warn("添加标签失败", zap.String("article_id", article.ID), zap.String("tag_id", tagID), zap.Error(err)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("创建文章成功", zap.String("id", article.ID), zap.String("title", article.Title)) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // UpdateArticle 更新文章 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) UpdateArticle(ctx context.Context, cmd *commands.UpdateArticleCommand) error { | 
					
						
							|  |  |  | 	// 1. 获取原文章 | 
					
						
							|  |  |  | 	article, err := s.articleRepo.GetByID(ctx, cmd.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取文章失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("文章不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 检查是否可以编辑 | 
					
						
							|  |  |  | 	if !article.CanEdit() { | 
					
						
							|  |  |  | 		return fmt.Errorf("文章状态不允许编辑") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 3. 更新字段 | 
					
						
							|  |  |  | 	if cmd.Title != "" { | 
					
						
							|  |  |  | 		article.Title = cmd.Title | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if cmd.Content != "" { | 
					
						
							|  |  |  | 		article.Content = cmd.Content | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if cmd.Summary != "" { | 
					
						
							|  |  |  | 		article.Summary = cmd.Summary | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if cmd.CoverImage != "" { | 
					
						
							|  |  |  | 		article.CoverImage = cmd.CoverImage | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if cmd.CategoryID != "" { | 
					
						
							|  |  |  | 		article.CategoryID = cmd.CategoryID | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	article.IsFeatured = cmd.IsFeatured | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 4. 验证更新后的文章 | 
					
						
							|  |  |  | 	if err := s.articleService.ValidateArticle(&article); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("业务验证失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 5. 保存更新 | 
					
						
							|  |  |  | 	if err := s.articleRepo.Update(ctx, article); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("更新文章失败", zap.String("id", article.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("更新文章失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 6. 处理标签关联 | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 	// 先清除现有标签 | 
					
						
							|  |  |  | 	existingTags, _ := s.tagRepo.GetArticleTags(ctx, article.ID) | 
					
						
							|  |  |  | 	for _, tag := range existingTags { | 
					
						
							|  |  |  | 		s.tagRepo.RemoveTagFromArticle(ctx, article.ID, tag.ID) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 添加新标签 | 
					
						
							|  |  |  | 	for _, tagID := range cmd.TagIDs { | 
					
						
							|  |  |  | 		if err := s.tagRepo.AddTagToArticle(ctx, article.ID, tagID); err != nil { | 
					
						
							|  |  |  | 			s.logger.Warn("添加标签失败", zap.String("article_id", article.ID), zap.String("tag_id", tagID), zap.Error(err)) | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("更新文章成功", zap.String("id", article.ID)) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // DeleteArticle 删除文章 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) DeleteArticle(ctx context.Context, cmd *commands.DeleteArticleCommand) error { | 
					
						
							|  |  |  | 	// 1. 检查文章是否存在 | 
					
						
							|  |  |  | 	_, err := s.articleRepo.GetByID(ctx, cmd.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取文章失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("文章不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 删除文章 | 
					
						
							|  |  |  | 	if err := s.articleRepo.Delete(ctx, cmd.ID); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("删除文章失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("删除文章失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("删除文章成功", zap.String("id", cmd.ID)) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // GetArticleByID 根据ID获取文章 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) GetArticleByID(ctx context.Context, query *appQueries.GetArticleQuery) (*responses.ArticleInfoResponse, error) { | 
					
						
							|  |  |  | 	// 1. 获取文章 | 
					
						
							|  |  |  | 	article, err := s.articleRepo.GetByID(ctx, query.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取文章失败", zap.String("id", query.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("文章不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 转换为响应对象 | 
					
						
							|  |  |  | 	response := responses.FromArticleEntity(&article) | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("获取文章成功", zap.String("id", article.ID)) | 
					
						
							|  |  |  | 	return response, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ListArticles 获取文章列表 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) ListArticles(ctx context.Context, query *appQueries.ListArticleQuery) (*responses.ArticleListResponse, error) { | 
					
						
							|  |  |  | 	// 1. 构建仓储查询 | 
					
						
							|  |  |  | 	repoQuery := &repoQueries.ListArticleQuery{ | 
					
						
							|  |  |  | 		Page:       query.Page, | 
					
						
							|  |  |  | 		PageSize:   query.PageSize, | 
					
						
							|  |  |  | 		Status:     query.Status, | 
					
						
							|  |  |  | 		CategoryID: query.CategoryID, | 
					
						
							|  |  |  | 		TagID:      query.TagID, | 
					
						
							|  |  |  | 		Title:      query.Title, | 
					
						
							|  |  |  | 		Summary:    query.Summary, | 
					
						
							|  |  |  | 		IsFeatured: query.IsFeatured, | 
					
						
							|  |  |  | 		OrderBy:    query.OrderBy, | 
					
						
							|  |  |  | 		OrderDir:   query.OrderDir, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 调用仓储 | 
					
						
							|  |  |  | 	articles, total, err := s.articleRepo.ListArticles(ctx, repoQuery) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取文章列表失败", zap.Error(err)) | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("获取文章列表失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 3. 转换为响应对象 | 
					
						
							| 
									
										
										
										
											2025-09-01 20:46:56 +08:00
										 |  |  | 	items := responses.FromArticleEntitiesToListItemList(articles) | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	response := &responses.ArticleListResponse{ | 
					
						
							|  |  |  | 		Total: total, | 
					
						
							|  |  |  | 		Page:  query.Page, | 
					
						
							|  |  |  | 		Size:  query.PageSize, | 
					
						
							|  |  |  | 		Items: items, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("获取文章列表成功", zap.Int64("total", total)) | 
					
						
							| 
									
										
										
										
											2025-09-01 21:15:15 +08:00
										 |  |  | 	return response, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ListArticlesForAdmin 获取文章列表(管理员端) | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) ListArticlesForAdmin(ctx context.Context, query *appQueries.ListArticleQuery) (*responses.ArticleListResponse, error) { | 
					
						
							|  |  |  | 	// 1. 构建仓储查询 | 
					
						
							|  |  |  | 	repoQuery := &repoQueries.ListArticleQuery{ | 
					
						
							|  |  |  | 		Page:       query.Page, | 
					
						
							|  |  |  | 		PageSize:   query.PageSize, | 
					
						
							|  |  |  | 		Status:     query.Status, | 
					
						
							|  |  |  | 		CategoryID: query.CategoryID, | 
					
						
							|  |  |  | 		TagID:      query.TagID, | 
					
						
							|  |  |  | 		Title:      query.Title, | 
					
						
							|  |  |  | 		Summary:    query.Summary, | 
					
						
							|  |  |  | 		IsFeatured: query.IsFeatured, | 
					
						
							|  |  |  | 		OrderBy:    query.OrderBy, | 
					
						
							|  |  |  | 		OrderDir:   query.OrderDir, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 21:15:15 +08:00
										 |  |  | 	// 2. 调用仓储 | 
					
						
							|  |  |  | 	articles, total, err := s.articleRepo.ListArticlesForAdmin(ctx, repoQuery) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取文章列表失败", zap.Error(err)) | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("获取文章列表失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 21:15:15 +08:00
										 |  |  | 	// 3. 转换为响应对象 | 
					
						
							|  |  |  | 	items := responses.FromArticleEntitiesToListItemList(articles) | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 21:15:15 +08:00
										 |  |  | 	response := &responses.ArticleListResponse{ | 
					
						
							|  |  |  | 		Total: total, | 
					
						
							|  |  |  | 		Page:  query.Page, | 
					
						
							|  |  |  | 		Size:  query.PageSize, | 
					
						
							|  |  |  | 		Items: items, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 21:15:15 +08:00
										 |  |  | 	s.logger.Info("获取文章列表成功", zap.Int64("total", total)) | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	return response, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // PublishArticle 发布文章 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) PublishArticle(ctx context.Context, cmd *commands.PublishArticleCommand) error { | 
					
						
							|  |  |  | 	// 1. 获取文章 | 
					
						
							|  |  |  | 	article, err := s.articleRepo.GetByID(ctx, cmd.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取文章失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("文章不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 发布文章 | 
					
						
							|  |  |  | 	if err := article.Publish(); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("发布文章失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 3. 保存更新 | 
					
						
							|  |  |  | 	if err := s.articleRepo.Update(ctx, article); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("更新文章失败", zap.String("id", article.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("发布文章失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("发布文章成功", zap.String("id", article.ID)) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // PublishArticleByID 通过ID发布文章 (用于定时任务) | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) PublishArticleByID(ctx context.Context, articleID string) error { | 
					
						
							|  |  |  | 	// 1. 获取文章 | 
					
						
							|  |  |  | 	article, err := s.articleRepo.GetByID(ctx, articleID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取文章失败", zap.String("id", articleID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("文章不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-03 13:51:52 +08:00
										 |  |  | 	// 2. 检查是否已取消定时发布 | 
					
						
							|  |  |  | 	if !article.IsScheduled() { | 
					
						
							|  |  |  | 		s.logger.Info("文章定时发布已取消,跳过执行", | 
					
						
							|  |  |  | 			zap.String("id", articleID), | 
					
						
							|  |  |  | 			zap.String("status", string(article.Status))) | 
					
						
							|  |  |  | 		return nil // 静默返回,不报错 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 3. 检查定时发布时间是否匹配 | 
					
						
							|  |  |  | 	if article.ScheduledAt == nil { | 
					
						
							|  |  |  | 		s.logger.Info("文章没有定时发布时间,跳过执行", | 
					
						
							|  |  |  | 			zap.String("id", articleID)) | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 4. 发布文章 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	if err := article.Publish(); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("发布文章失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-03 13:51:52 +08:00
										 |  |  | 	// 5. 保存更新 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	if err := s.articleRepo.Update(ctx, article); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("更新文章失败", zap.String("id", article.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("发布文章失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("定时发布文章成功", zap.String("id", article.ID)) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SchedulePublishArticle 定时发布文章 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) SchedulePublishArticle(ctx context.Context, cmd *commands.SchedulePublishCommand) error { | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 	// 1. 解析定时发布时间 | 
					
						
							|  |  |  | 	scheduledTime, err := cmd.GetScheduledTime() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("解析定时发布时间失败", zap.String("scheduled_time", cmd.ScheduledTime), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("定时发布时间格式错误: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 2. 获取文章 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	article, err := s.articleRepo.GetByID(ctx, cmd.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取文章失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("文章不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 	// 3. 取消旧任务 | 
					
						
							|  |  |  | 	if err := s.taskManager.CancelTask(ctx, cmd.ID); err != nil { | 
					
						
							|  |  |  | 		s.logger.Warn("取消旧任务失败", zap.String("article_id", cmd.ID), zap.Error(err)) | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 	// 4. 创建任务工厂 | 
					
						
							|  |  |  | 	taskFactory := task_entities.NewTaskFactoryWithManager(s.taskManager) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 5. 创建并异步入队文章发布任务 | 
					
						
							|  |  |  | 	if err := taskFactory.CreateAndEnqueueArticlePublishTask( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							|  |  |  | 		cmd.ID, | 
					
						
							|  |  |  | 		scheduledTime, | 
					
						
							|  |  |  | 		"system", // 暂时使用系统用户ID | 
					
						
							|  |  |  | 	); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("创建并入队文章发布任务失败", zap.Error(err)) | 
					
						
							|  |  |  | 		return err | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 	// 6. 设置定时发布 | 
					
						
							|  |  |  | 	if err := article.SchedulePublish(scheduledTime); err != nil { | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 		return fmt.Errorf("设置定时发布失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 	// 7. 保存更新 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	if err := s.articleRepo.Update(ctx, article); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("更新文章失败", zap.String("id", article.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("设置定时发布失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 	s.logger.Info("设置定时发布成功", zap.String("id", article.ID), zap.Time("scheduled_time", scheduledTime)) | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CancelSchedulePublishArticle 取消定时发布文章 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) CancelSchedulePublishArticle(ctx context.Context, cmd *commands.CancelScheduleCommand) error { | 
					
						
							|  |  |  | 	// 1. 获取文章 | 
					
						
							|  |  |  | 	article, err := s.articleRepo.GetByID(ctx, cmd.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取文章失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("文章不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 2. 检查是否已设置定时发布 | 
					
						
							|  |  |  | 	if !article.IsScheduled() { | 
					
						
							|  |  |  | 		return fmt.Errorf("文章未设置定时发布") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 3. 取消定时任务 | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 	if err := s.taskManager.CancelTask(ctx, cmd.ID); err != nil { | 
					
						
							|  |  |  | 		s.logger.Warn("取消定时任务失败", zap.String("article_id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		// 不返回错误,继续执行取消定时发布 | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 4. 取消定时发布 | 
					
						
							|  |  |  | 	if err := article.CancelSchedulePublish(); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("取消定时发布失败: %w", err) | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// 5. 保存更新 | 
					
						
							|  |  |  | 	if err := s.articleRepo.Update(ctx, article); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("更新文章失败", zap.String("id", article.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("取消定时发布失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	s.logger.Info("取消定时发布成功", zap.String("id", article.ID)) | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ArchiveArticle 归档文章 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) ArchiveArticle(ctx context.Context, cmd *commands.ArchiveArticleCommand) error { | 
					
						
							|  |  |  | 	// 1. 获取文章 | 
					
						
							|  |  |  | 	article, err := s.articleRepo.GetByID(ctx, cmd.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取文章失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("文章不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 归档文章 | 
					
						
							|  |  |  | 	if err := article.Archive(); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("归档文章失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 3. 保存更新 | 
					
						
							|  |  |  | 	if err := s.articleRepo.Update(ctx, article); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("更新文章失败", zap.String("id", article.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("归档文章失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("归档文章成功", zap.String("id", article.ID)) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SetFeatured 设置推荐状态 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) SetFeatured(ctx context.Context, cmd *commands.SetFeaturedCommand) error { | 
					
						
							|  |  |  | 	// 1. 获取文章 | 
					
						
							|  |  |  | 	article, err := s.articleRepo.GetByID(ctx, cmd.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取文章失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("文章不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 设置推荐状态 | 
					
						
							|  |  |  | 	article.SetFeatured(cmd.IsFeatured) | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 3. 保存更新 | 
					
						
							|  |  |  | 	if err := s.articleRepo.Update(ctx, article); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("更新文章失败", zap.String("id", article.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("设置推荐状态失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("设置推荐状态成功", zap.String("id", article.ID), zap.Bool("is_featured", cmd.IsFeatured)) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RecordView 记录阅读 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) RecordView(ctx context.Context, articleID string, userID string, ipAddress string, userAgent string) error { | 
					
						
							|  |  |  | 	// 1. 增加阅读量 | 
					
						
							|  |  |  | 	if err := s.articleRepo.IncrementViewCount(ctx, articleID); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("增加阅读量失败", zap.String("id", articleID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("记录阅读失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("记录阅读成功", zap.String("id", articleID)) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // GetArticleStats 获取文章统计 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) GetArticleStats(ctx context.Context) (*responses.ArticleStatsResponse, error) { | 
					
						
							|  |  |  | 	// 1. 获取各种统计 | 
					
						
							|  |  |  | 	totalArticles, err := s.articleRepo.CountByStatus(ctx, "") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取文章总数失败", zap.Error(err)) | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("获取统计信息失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	publishedArticles, err := s.articleRepo.CountByStatus(ctx, entities.ArticleStatusPublished) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取已发布文章数失败", zap.Error(err)) | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("获取统计信息失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	draftArticles, err := s.articleRepo.CountByStatus(ctx, entities.ArticleStatusDraft) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取草稿文章数失败", zap.Error(err)) | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("获取统计信息失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	archivedArticles, err := s.articleRepo.CountByStatus(ctx, entities.ArticleStatusArchived) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取归档文章数失败", zap.Error(err)) | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("获取统计信息失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	response := &responses.ArticleStatsResponse{ | 
					
						
							|  |  |  | 		TotalArticles:     totalArticles, | 
					
						
							|  |  |  | 		PublishedArticles: publishedArticles, | 
					
						
							|  |  |  | 		DraftArticles:     draftArticles, | 
					
						
							|  |  |  | 		ArchivedArticles:  archivedArticles, | 
					
						
							|  |  |  | 		TotalViews:        0, // TODO: 实现总阅读量统计 | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("获取文章统计成功") | 
					
						
							|  |  |  | 	return response, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // validateCreateArticle 验证创建文章参数 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) validateCreateArticle(cmd *commands.CreateArticleCommand) error { | 
					
						
							|  |  |  | 	if cmd.Title == "" { | 
					
						
							|  |  |  | 		return fmt.Errorf("文章标题不能为空") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if cmd.Content == "" { | 
					
						
							|  |  |  | 		return fmt.Errorf("文章内容不能为空") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ==================== 分类相关方法 ==================== | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CreateCategory 创建分类 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) CreateCategory(ctx context.Context, cmd *commands.CreateCategoryCommand) error { | 
					
						
							|  |  |  | 	// 1. 参数验证 | 
					
						
							|  |  |  | 	if err := s.validateCreateCategory(cmd); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("参数验证失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 创建分类实体 | 
					
						
							|  |  |  | 	category := &entities.Category{ | 
					
						
							|  |  |  | 		Name:        cmd.Name, | 
					
						
							|  |  |  | 		Description: cmd.Description, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 3. 保存分类 | 
					
						
							|  |  |  | 	_, err := s.categoryRepo.Create(ctx, *category) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("创建分类失败", zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("创建分类失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("创建分类成功", zap.String("id", category.ID), zap.String("name", category.Name)) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // UpdateCategory 更新分类 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) UpdateCategory(ctx context.Context, cmd *commands.UpdateCategoryCommand) error { | 
					
						
							|  |  |  | 	// 1. 获取原分类 | 
					
						
							|  |  |  | 	category, err := s.categoryRepo.GetByID(ctx, cmd.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取分类失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("分类不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 更新字段 | 
					
						
							|  |  |  | 	category.Name = cmd.Name | 
					
						
							|  |  |  | 	category.Description = cmd.Description | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 3. 保存更新 | 
					
						
							|  |  |  | 	if err := s.categoryRepo.Update(ctx, category); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("更新分类失败", zap.String("id", category.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("更新分类失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("更新分类成功", zap.String("id", category.ID)) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // DeleteCategory 删除分类 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) DeleteCategory(ctx context.Context, cmd *commands.DeleteCategoryCommand) error { | 
					
						
							|  |  |  | 	// 1. 检查分类是否存在 | 
					
						
							|  |  |  | 	category, err := s.categoryRepo.GetByID(ctx, cmd.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取分类失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("分类不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 检查是否有文章使用此分类 | 
					
						
							|  |  |  | 	count, err := s.articleRepo.CountByCategoryID(ctx, cmd.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("检查分类使用情况失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("删除分类失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	if count > 0 { | 
					
						
							|  |  |  | 		return fmt.Errorf("该分类下还有 %d 篇文章,无法删除", count) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 3. 删除分类 | 
					
						
							|  |  |  | 	if err := s.categoryRepo.Delete(ctx, cmd.ID); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("删除分类失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("删除分类失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("删除分类成功", zap.String("id", cmd.ID), zap.String("name", category.Name)) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // GetCategoryByID 获取分类详情 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) GetCategoryByID(ctx context.Context, query *appQueries.GetCategoryQuery) (*responses.CategoryInfoResponse, error) { | 
					
						
							|  |  |  | 	// 1. 获取分类 | 
					
						
							|  |  |  | 	category, err := s.categoryRepo.GetByID(ctx, query.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取分类失败", zap.String("id", query.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("分类不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 转换为响应对象 | 
					
						
							|  |  |  | 	response := &responses.CategoryInfoResponse{ | 
					
						
							|  |  |  | 		ID:          category.ID, | 
					
						
							|  |  |  | 		Name:        category.Name, | 
					
						
							|  |  |  | 		Description: category.Description, | 
					
						
							|  |  |  | 		SortOrder:   category.SortOrder, | 
					
						
							|  |  |  | 		CreatedAt:   category.CreatedAt, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	return response, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ListCategories 获取分类列表 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) ListCategories(ctx context.Context) (*responses.CategoryListResponse, error) { | 
					
						
							|  |  |  | 	// 1. 获取分类列表 | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 	categories, err := s.categoryRepo.List(ctx, shared_interfaces.ListOptions{}) | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取分类列表失败", zap.Error(err)) | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("获取分类列表失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 转换为响应对象 | 
					
						
							|  |  |  | 	items := make([]responses.CategoryInfoResponse, len(categories)) | 
					
						
							|  |  |  | 	for i, category := range categories { | 
					
						
							|  |  |  | 		items[i] = responses.CategoryInfoResponse{ | 
					
						
							|  |  |  | 			ID:          category.ID, | 
					
						
							|  |  |  | 			Name:        category.Name, | 
					
						
							|  |  |  | 			Description: category.Description, | 
					
						
							|  |  |  | 			SortOrder:   category.SortOrder, | 
					
						
							|  |  |  | 			CreatedAt:   category.CreatedAt, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	response := &responses.CategoryListResponse{ | 
					
						
							|  |  |  | 		Items: items, | 
					
						
							|  |  |  | 		Total: len(items), | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	return response, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ==================== 标签相关方法 ==================== | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CreateTag 创建标签 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) CreateTag(ctx context.Context, cmd *commands.CreateTagCommand) error { | 
					
						
							|  |  |  | 	// 1. 参数验证 | 
					
						
							|  |  |  | 	if err := s.validateCreateTag(cmd); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("参数验证失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 创建标签实体 | 
					
						
							|  |  |  | 	tag := &entities.Tag{ | 
					
						
							|  |  |  | 		Name:  cmd.Name, | 
					
						
							|  |  |  | 		Color: cmd.Color, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 3. 保存标签 | 
					
						
							|  |  |  | 	_, err := s.tagRepo.Create(ctx, *tag) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("创建标签失败", zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("创建标签失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("创建标签成功", zap.String("id", tag.ID), zap.String("name", tag.Name)) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // UpdateTag 更新标签 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) UpdateTag(ctx context.Context, cmd *commands.UpdateTagCommand) error { | 
					
						
							|  |  |  | 	// 1. 获取原标签 | 
					
						
							|  |  |  | 	tag, err := s.tagRepo.GetByID(ctx, cmd.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取标签失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("标签不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 更新字段 | 
					
						
							|  |  |  | 	tag.Name = cmd.Name | 
					
						
							|  |  |  | 	tag.Color = cmd.Color | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 3. 保存更新 | 
					
						
							|  |  |  | 	if err := s.tagRepo.Update(ctx, tag); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("更新标签失败", zap.String("id", tag.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("更新标签失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("更新标签成功", zap.String("id", tag.ID)) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // DeleteTag 删除标签 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) DeleteTag(ctx context.Context, cmd *commands.DeleteTagCommand) error { | 
					
						
							|  |  |  | 	// 1. 检查标签是否存在 | 
					
						
							|  |  |  | 	tag, err := s.tagRepo.GetByID(ctx, cmd.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取标签失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("标签不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 删除标签 | 
					
						
							|  |  |  | 	if err := s.tagRepo.Delete(ctx, cmd.ID); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("删除标签失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("删除标签失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	s.logger.Info("删除标签成功", zap.String("id", cmd.ID), zap.String("name", tag.Name)) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // GetTagByID 获取标签详情 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) GetTagByID(ctx context.Context, query *appQueries.GetTagQuery) (*responses.TagInfoResponse, error) { | 
					
						
							|  |  |  | 	// 1. 获取标签 | 
					
						
							|  |  |  | 	tag, err := s.tagRepo.GetByID(ctx, query.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取标签失败", zap.String("id", query.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("标签不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 转换为响应对象 | 
					
						
							|  |  |  | 	response := &responses.TagInfoResponse{ | 
					
						
							|  |  |  | 		ID:        tag.ID, | 
					
						
							|  |  |  | 		Name:      tag.Name, | 
					
						
							|  |  |  | 		Color:     tag.Color, | 
					
						
							|  |  |  | 		CreatedAt: tag.CreatedAt, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	return response, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ListTags 获取标签列表 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) ListTags(ctx context.Context) (*responses.TagListResponse, error) { | 
					
						
							|  |  |  | 	// 1. 获取标签列表 | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 	tags, err := s.tagRepo.List(ctx, shared_interfaces.ListOptions{}) | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取标签列表失败", zap.Error(err)) | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("获取标签列表失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	// 2. 转换为响应对象 | 
					
						
							|  |  |  | 	items := make([]responses.TagInfoResponse, len(tags)) | 
					
						
							|  |  |  | 	for i, tag := range tags { | 
					
						
							|  |  |  | 		items[i] = responses.TagInfoResponse{ | 
					
						
							|  |  |  | 			ID:        tag.ID, | 
					
						
							|  |  |  | 			Name:      tag.Name, | 
					
						
							|  |  |  | 			Color:     tag.Color, | 
					
						
							|  |  |  | 			CreatedAt: tag.CreatedAt, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	response := &responses.TagListResponse{ | 
					
						
							|  |  |  | 		Items: items, | 
					
						
							|  |  |  | 		Total: len(items), | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | 	return response, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-03 13:51:52 +08:00
										 |  |  | // UpdateSchedulePublishArticle 修改定时发布时间 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) UpdateSchedulePublishArticle(ctx context.Context, cmd *commands.SchedulePublishCommand) error { | 
					
						
							|  |  |  | 	// 1. 解析定时发布时间 | 
					
						
							|  |  |  | 	scheduledTime, err := cmd.GetScheduledTime() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("解析定时发布时间失败", zap.String("scheduled_time", cmd.ScheduledTime), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("定时发布时间格式错误: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 2. 获取文章 | 
					
						
							|  |  |  | 	article, err := s.articleRepo.GetByID(ctx, cmd.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("获取文章失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("文章不存在: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 3. 检查是否已设置定时发布 | 
					
						
							|  |  |  | 	if !article.IsScheduled() { | 
					
						
							|  |  |  | 		return fmt.Errorf("文章未设置定时发布,无法修改时间") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 	// 4. 更新数据库中的任务调度时间 | 
					
						
							|  |  |  | 	if err := s.taskManager.UpdateTaskSchedule(ctx, cmd.ID, scheduledTime); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("更新任务调度时间失败", zap.String("id", cmd.ID), zap.Error(err)) | 
					
						
							| 
									
										
										
										
											2025-09-03 13:51:52 +08:00
										 |  |  | 		return fmt.Errorf("修改定时发布时间失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 5. 更新定时发布 | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 	if err := article.UpdateSchedulePublish(scheduledTime); err != nil { | 
					
						
							| 
									
										
										
										
											2025-09-03 13:51:52 +08:00
										 |  |  | 		return fmt.Errorf("更新定时发布失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 6. 保存更新 | 
					
						
							|  |  |  | 	if err := s.articleRepo.Update(ctx, article); err != nil { | 
					
						
							|  |  |  | 		s.logger.Error("更新文章失败", zap.String("id", article.ID), zap.Error(err)) | 
					
						
							|  |  |  | 		return fmt.Errorf("修改定时发布时间失败: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	s.logger.Info("修改定时发布时间成功",  | 
					
						
							|  |  |  | 		zap.String("id", article.ID),  | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  | 		zap.Time("new_scheduled_time", scheduledTime)) | 
					
						
							| 
									
										
										
										
											2025-09-03 13:51:52 +08:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  | // ==================== 验证方法 ==================== | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // validateCreateCategory 验证创建分类参数 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) validateCreateCategory(cmd *commands.CreateCategoryCommand) error { | 
					
						
							|  |  |  | 	if cmd.Name == "" { | 
					
						
							|  |  |  | 		return fmt.Errorf("分类名称不能为空") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if len(cmd.Name) > 50 { | 
					
						
							|  |  |  | 		return fmt.Errorf("分类名称长度不能超过50个字符") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if len(cmd.Description) > 200 { | 
					
						
							|  |  |  | 		return fmt.Errorf("分类描述长度不能超过200个字符") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // validateCreateTag 验证创建标签参数 | 
					
						
							|  |  |  | func (s *ArticleApplicationServiceImpl) validateCreateTag(cmd *commands.CreateTagCommand) error { | 
					
						
							|  |  |  | 	if cmd.Name == "" { | 
					
						
							|  |  |  | 		return fmt.Errorf("标签名称不能为空") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if len(cmd.Name) > 30 { | 
					
						
							|  |  |  | 		return fmt.Errorf("标签名称长度不能超过30个字符") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if cmd.Color == "" { | 
					
						
							|  |  |  | 		return fmt.Errorf("标签颜色不能为空") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// TODO: 添加十六进制颜色格式验证 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } |