|
|
|
|
@@ -41,7 +41,7 @@ func NewArticleApplicationService(
|
|
|
|
|
tagRepo: tagRepo,
|
|
|
|
|
articleService: articleService,
|
|
|
|
|
asynqClient: asynqClient,
|
|
|
|
|
logger: logger,
|
|
|
|
|
logger: logger,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -51,7 +51,7 @@ func (s *ArticleApplicationServiceImpl) CreateArticle(ctx context.Context, cmd *
|
|
|
|
|
if err := s.validateCreateArticle(cmd); err != nil {
|
|
|
|
|
return fmt.Errorf("参数验证失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 创建文章实体
|
|
|
|
|
article := &entities.Article{
|
|
|
|
|
Title: cmd.Title,
|
|
|
|
|
@@ -62,19 +62,19 @@ func (s *ArticleApplicationServiceImpl) CreateArticle(ctx context.Context, cmd *
|
|
|
|
|
IsFeatured: cmd.IsFeatured,
|
|
|
|
|
Status: entities.ArticleStatusDraft,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 调用领域服务验证
|
|
|
|
|
if err := s.articleService.ValidateArticle(article); err != nil {
|
|
|
|
|
return fmt.Errorf("业务验证失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 保存文章
|
|
|
|
|
_, err := s.articleRepo.Create(ctx, *article)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.logger.Error("创建文章失败", zap.Error(err))
|
|
|
|
|
return fmt.Errorf("创建文章失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 处理标签关联
|
|
|
|
|
if len(cmd.TagIDs) > 0 {
|
|
|
|
|
for _, tagID := range cmd.TagIDs {
|
|
|
|
|
@@ -83,7 +83,7 @@ func (s *ArticleApplicationServiceImpl) CreateArticle(ctx context.Context, cmd *
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("创建文章成功", zap.String("id", article.ID), zap.String("title", article.Title))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
@@ -96,12 +96,12 @@ func (s *ArticleApplicationServiceImpl) UpdateArticle(ctx context.Context, cmd *
|
|
|
|
|
s.logger.Error("获取文章失败", zap.String("id", cmd.ID), zap.Error(err))
|
|
|
|
|
return fmt.Errorf("文章不存在: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 检查是否可以编辑
|
|
|
|
|
if !article.CanEdit() {
|
|
|
|
|
return fmt.Errorf("文章状态不允许编辑")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 更新字段
|
|
|
|
|
if cmd.Title != "" {
|
|
|
|
|
article.Title = cmd.Title
|
|
|
|
|
@@ -119,34 +119,32 @@ func (s *ArticleApplicationServiceImpl) UpdateArticle(ctx context.Context, cmd *
|
|
|
|
|
article.CategoryID = cmd.CategoryID
|
|
|
|
|
}
|
|
|
|
|
article.IsFeatured = cmd.IsFeatured
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 验证更新后的文章
|
|
|
|
|
if err := s.articleService.ValidateArticle(&article); err != nil {
|
|
|
|
|
return fmt.Errorf("业务验证失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 处理标签关联
|
|
|
|
|
if len(cmd.TagIDs) > 0 {
|
|
|
|
|
// 先清除现有标签
|
|
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
// 先清除现有标签
|
|
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("更新文章成功", zap.String("id", article.ID))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
@@ -159,13 +157,13 @@ func (s *ArticleApplicationServiceImpl) DeleteArticle(ctx context.Context, cmd *
|
|
|
|
|
s.logger.Error("获取文章失败", zap.String("id", cmd.ID), zap.Error(err))
|
|
|
|
|
return fmt.Errorf("文章不存在: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("删除文章成功", zap.String("id", cmd.ID))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
@@ -178,10 +176,10 @@ func (s *ArticleApplicationServiceImpl) GetArticleByID(ctx context.Context, quer
|
|
|
|
|
s.logger.Error("获取文章失败", zap.String("id", query.ID), zap.Error(err))
|
|
|
|
|
return nil, fmt.Errorf("文章不存在: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 转换为响应对象
|
|
|
|
|
response := responses.FromArticleEntity(&article)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("获取文章成功", zap.String("id", article.ID))
|
|
|
|
|
return response, nil
|
|
|
|
|
}
|
|
|
|
|
@@ -201,24 +199,24 @@ func (s *ArticleApplicationServiceImpl) ListArticles(ctx context.Context, query
|
|
|
|
|
OrderBy: query.OrderBy,
|
|
|
|
|
OrderDir: query.OrderDir,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 调用仓储
|
|
|
|
|
articles, total, err := s.articleRepo.ListArticles(ctx, repoQuery)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.logger.Error("获取文章列表失败", zap.Error(err))
|
|
|
|
|
return nil, fmt.Errorf("获取文章列表失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 转换为响应对象
|
|
|
|
|
items := responses.FromArticleEntitiesToListItemList(articles)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
response := &responses.ArticleListResponse{
|
|
|
|
|
Total: total,
|
|
|
|
|
Page: query.Page,
|
|
|
|
|
Size: query.PageSize,
|
|
|
|
|
Items: items,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("获取文章列表成功", zap.Int64("total", total))
|
|
|
|
|
return response, nil
|
|
|
|
|
}
|
|
|
|
|
@@ -238,29 +236,28 @@ func (s *ArticleApplicationServiceImpl) ListArticlesForAdmin(ctx context.Context
|
|
|
|
|
OrderBy: query.OrderBy,
|
|
|
|
|
OrderDir: query.OrderDir,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 调用仓储
|
|
|
|
|
articles, total, err := s.articleRepo.ListArticlesForAdmin(ctx, repoQuery)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.logger.Error("获取文章列表失败", zap.Error(err))
|
|
|
|
|
return nil, fmt.Errorf("获取文章列表失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 转换为响应对象
|
|
|
|
|
items := responses.FromArticleEntitiesToListItemList(articles)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
response := &responses.ArticleListResponse{
|
|
|
|
|
Total: total,
|
|
|
|
|
Page: query.Page,
|
|
|
|
|
Size: query.PageSize,
|
|
|
|
|
Items: items,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("获取文章列表成功", zap.Int64("total", total))
|
|
|
|
|
return response, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// PublishArticle 发布文章
|
|
|
|
|
func (s *ArticleApplicationServiceImpl) PublishArticle(ctx context.Context, cmd *commands.PublishArticleCommand) error {
|
|
|
|
|
// 1. 获取文章
|
|
|
|
|
@@ -269,18 +266,18 @@ func (s *ArticleApplicationServiceImpl) PublishArticle(ctx context.Context, cmd
|
|
|
|
|
s.logger.Error("获取文章失败", zap.String("id", cmd.ID), zap.Error(err))
|
|
|
|
|
return fmt.Errorf("文章不存在: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 发布文章
|
|
|
|
|
if err := article.Publish(); err != nil {
|
|
|
|
|
return fmt.Errorf("发布文章失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("发布文章成功", zap.String("id", article.ID))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
@@ -293,49 +290,100 @@ func (s *ArticleApplicationServiceImpl) PublishArticleByID(ctx context.Context,
|
|
|
|
|
s.logger.Error("获取文章失败", zap.String("id", articleID), zap.Error(err))
|
|
|
|
|
return fmt.Errorf("文章不存在: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 发布文章
|
|
|
|
|
if err := article.Publish(); err != nil {
|
|
|
|
|
return fmt.Errorf("发布文章失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("定时发布文章成功", zap.String("id", article.ID))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SchedulePublishArticle 定时发布文章
|
|
|
|
|
func (s *ArticleApplicationServiceImpl) SchedulePublishArticle(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.TaskID != "" {
|
|
|
|
|
if err := s.asynqClient.CancelScheduledTask(ctx, article.TaskID); err != nil {
|
|
|
|
|
s.logger.Warn("取消旧定时任务失败", zap.String("task_id", article.TaskID), zap.Error(err))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. 调度定时发布任务
|
|
|
|
|
taskID, err := s.asynqClient.ScheduleArticlePublish(ctx, cmd.ID, scheduledTime)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.logger.Error("调度定时发布任务失败", zap.String("id", cmd.ID), zap.Error(err))
|
|
|
|
|
return fmt.Errorf("调度定时发布任务失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. 设置定时发布
|
|
|
|
|
if err := article.SchedulePublish(scheduledTime, taskID); err != nil {
|
|
|
|
|
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), zap.Time("scheduled_time", scheduledTime), zap.String("task_id", taskID))
|
|
|
|
|
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 err := article.SchedulePublish(cmd.ScheduledTime); err != nil {
|
|
|
|
|
return fmt.Errorf("设置定时发布失败: %w", err)
|
|
|
|
|
|
|
|
|
|
// 2. 检查是否已设置定时发布
|
|
|
|
|
if !article.IsScheduled() {
|
|
|
|
|
return fmt.Errorf("文章未设置定时发布")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 保存更新
|
|
|
|
|
|
|
|
|
|
// 3. 取消定时任务
|
|
|
|
|
if article.TaskID != "" {
|
|
|
|
|
if err := s.asynqClient.CancelScheduledTask(ctx, article.TaskID); err != nil {
|
|
|
|
|
s.logger.Warn("取消定时任务失败", zap.String("task_id", article.TaskID), zap.Error(err))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. 取消定时发布
|
|
|
|
|
if err := article.CancelSchedulePublish(); err != nil {
|
|
|
|
|
return fmt.Errorf("取消定时发布失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
return fmt.Errorf("取消定时发布失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. 调度定时发布任务
|
|
|
|
|
if err := s.asynqClient.ScheduleArticlePublish(ctx, cmd.ID, cmd.ScheduledTime); err != nil {
|
|
|
|
|
s.logger.Error("调度定时发布任务失败", zap.String("id", cmd.ID), zap.Error(err))
|
|
|
|
|
return fmt.Errorf("调度定时发布任务失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.logger.Info("设置定时发布成功", zap.String("id", article.ID), zap.Time("scheduled_time", cmd.ScheduledTime))
|
|
|
|
|
|
|
|
|
|
s.logger.Info("取消定时发布成功", zap.String("id", article.ID))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -347,18 +395,18 @@ func (s *ArticleApplicationServiceImpl) ArchiveArticle(ctx context.Context, cmd
|
|
|
|
|
s.logger.Error("获取文章失败", zap.String("id", cmd.ID), zap.Error(err))
|
|
|
|
|
return fmt.Errorf("文章不存在: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 归档文章
|
|
|
|
|
if err := article.Archive(); err != nil {
|
|
|
|
|
return fmt.Errorf("归档文章失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("归档文章成功", zap.String("id", article.ID))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
@@ -371,22 +419,20 @@ func (s *ArticleApplicationServiceImpl) SetFeatured(ctx context.Context, cmd *co
|
|
|
|
|
s.logger.Error("获取文章失败", zap.String("id", cmd.ID), zap.Error(err))
|
|
|
|
|
return fmt.Errorf("文章不存在: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 设置推荐状态
|
|
|
|
|
article.SetFeatured(cmd.IsFeatured)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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. 增加阅读量
|
|
|
|
|
@@ -394,7 +440,7 @@ func (s *ArticleApplicationServiceImpl) RecordView(ctx context.Context, articleI
|
|
|
|
|
s.logger.Error("增加阅读量失败", zap.String("id", articleID), zap.Error(err))
|
|
|
|
|
return fmt.Errorf("记录阅读失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("记录阅读成功", zap.String("id", articleID))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
@@ -407,25 +453,25 @@ func (s *ArticleApplicationServiceImpl) GetArticleStats(ctx context.Context) (*r
|
|
|
|
|
s.logger.Error("获取文章总数失败", zap.Error(err))
|
|
|
|
|
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
publishedArticles, err := s.articleRepo.CountByStatus(ctx, entities.ArticleStatusPublished)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.logger.Error("获取已发布文章数失败", zap.Error(err))
|
|
|
|
|
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
draftArticles, err := s.articleRepo.CountByStatus(ctx, entities.ArticleStatusDraft)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.logger.Error("获取草稿文章数失败", zap.Error(err))
|
|
|
|
|
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
archivedArticles, err := s.articleRepo.CountByStatus(ctx, entities.ArticleStatusArchived)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.logger.Error("获取归档文章数失败", zap.Error(err))
|
|
|
|
|
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
response := &responses.ArticleStatsResponse{
|
|
|
|
|
TotalArticles: totalArticles,
|
|
|
|
|
PublishedArticles: publishedArticles,
|
|
|
|
|
@@ -433,12 +479,11 @@ func (s *ArticleApplicationServiceImpl) GetArticleStats(ctx context.Context) (*r
|
|
|
|
|
ArchivedArticles: archivedArticles,
|
|
|
|
|
TotalViews: 0, // TODO: 实现总阅读量统计
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("获取文章统计成功")
|
|
|
|
|
return response, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// validateCreateArticle 验证创建文章参数
|
|
|
|
|
func (s *ArticleApplicationServiceImpl) validateCreateArticle(cmd *commands.CreateArticleCommand) error {
|
|
|
|
|
if cmd.Title == "" {
|
|
|
|
|
@@ -458,20 +503,20 @@ func (s *ArticleApplicationServiceImpl) CreateCategory(ctx context.Context, cmd
|
|
|
|
|
if err := s.validateCreateCategory(cmd); err != nil {
|
|
|
|
|
return fmt.Errorf("参数验证失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 创建分类实体
|
|
|
|
|
category := &entities.Category{
|
|
|
|
|
Name: cmd.Name,
|
|
|
|
|
Description: cmd.Description,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 保存分类
|
|
|
|
|
_, err := s.categoryRepo.Create(ctx, *category)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.logger.Error("创建分类失败", zap.Error(err))
|
|
|
|
|
return fmt.Errorf("创建分类失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("创建分类成功", zap.String("id", category.ID), zap.String("name", category.Name))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
@@ -484,17 +529,17 @@ func (s *ArticleApplicationServiceImpl) UpdateCategory(ctx context.Context, cmd
|
|
|
|
|
s.logger.Error("获取分类失败", zap.String("id", cmd.ID), zap.Error(err))
|
|
|
|
|
return fmt.Errorf("分类不存在: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 更新字段
|
|
|
|
|
category.Name = cmd.Name
|
|
|
|
|
category.Description = cmd.Description
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("更新分类成功", zap.String("id", category.ID))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
@@ -507,24 +552,24 @@ func (s *ArticleApplicationServiceImpl) DeleteCategory(ctx context.Context, cmd
|
|
|
|
|
s.logger.Error("获取分类失败", zap.String("id", cmd.ID), zap.Error(err))
|
|
|
|
|
return fmt.Errorf("分类不存在: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if count > 0 {
|
|
|
|
|
return fmt.Errorf("该分类下还有 %d 篇文章,无法删除", count)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("删除分类成功", zap.String("id", cmd.ID), zap.String("name", category.Name))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
@@ -537,7 +582,7 @@ func (s *ArticleApplicationServiceImpl) GetCategoryByID(ctx context.Context, que
|
|
|
|
|
s.logger.Error("获取分类失败", zap.String("id", query.ID), zap.Error(err))
|
|
|
|
|
return nil, fmt.Errorf("分类不存在: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 转换为响应对象
|
|
|
|
|
response := &responses.CategoryInfoResponse{
|
|
|
|
|
ID: category.ID,
|
|
|
|
|
@@ -546,7 +591,7 @@ func (s *ArticleApplicationServiceImpl) GetCategoryByID(ctx context.Context, que
|
|
|
|
|
SortOrder: category.SortOrder,
|
|
|
|
|
CreatedAt: category.CreatedAt,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return response, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -558,7 +603,7 @@ func (s *ArticleApplicationServiceImpl) ListCategories(ctx context.Context) (*re
|
|
|
|
|
s.logger.Error("获取分类列表失败", zap.Error(err))
|
|
|
|
|
return nil, fmt.Errorf("获取分类列表失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 转换为响应对象
|
|
|
|
|
items := make([]responses.CategoryInfoResponse, len(categories))
|
|
|
|
|
for i, category := range categories {
|
|
|
|
|
@@ -570,12 +615,12 @@ func (s *ArticleApplicationServiceImpl) ListCategories(ctx context.Context) (*re
|
|
|
|
|
CreatedAt: category.CreatedAt,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
response := &responses.CategoryListResponse{
|
|
|
|
|
Items: items,
|
|
|
|
|
Total: len(items),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return response, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -587,20 +632,20 @@ func (s *ArticleApplicationServiceImpl) CreateTag(ctx context.Context, cmd *comm
|
|
|
|
|
if err := s.validateCreateTag(cmd); err != nil {
|
|
|
|
|
return fmt.Errorf("参数验证失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 创建标签实体
|
|
|
|
|
tag := &entities.Tag{
|
|
|
|
|
Name: cmd.Name,
|
|
|
|
|
Color: cmd.Color,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 保存标签
|
|
|
|
|
_, err := s.tagRepo.Create(ctx, *tag)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.logger.Error("创建标签失败", zap.Error(err))
|
|
|
|
|
return fmt.Errorf("创建标签失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("创建标签成功", zap.String("id", tag.ID), zap.String("name", tag.Name))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
@@ -613,17 +658,17 @@ func (s *ArticleApplicationServiceImpl) UpdateTag(ctx context.Context, cmd *comm
|
|
|
|
|
s.logger.Error("获取标签失败", zap.String("id", cmd.ID), zap.Error(err))
|
|
|
|
|
return fmt.Errorf("标签不存在: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 更新字段
|
|
|
|
|
tag.Name = cmd.Name
|
|
|
|
|
tag.Color = cmd.Color
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("更新标签成功", zap.String("id", tag.ID))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
@@ -636,13 +681,13 @@ func (s *ArticleApplicationServiceImpl) DeleteTag(ctx context.Context, cmd *comm
|
|
|
|
|
s.logger.Error("获取标签失败", zap.String("id", cmd.ID), zap.Error(err))
|
|
|
|
|
return fmt.Errorf("标签不存在: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("删除标签成功", zap.String("id", cmd.ID), zap.String("name", tag.Name))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
@@ -655,7 +700,7 @@ func (s *ArticleApplicationServiceImpl) GetTagByID(ctx context.Context, query *a
|
|
|
|
|
s.logger.Error("获取标签失败", zap.String("id", query.ID), zap.Error(err))
|
|
|
|
|
return nil, fmt.Errorf("标签不存在: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 转换为响应对象
|
|
|
|
|
response := &responses.TagInfoResponse{
|
|
|
|
|
ID: tag.ID,
|
|
|
|
|
@@ -663,7 +708,7 @@ func (s *ArticleApplicationServiceImpl) GetTagByID(ctx context.Context, query *a
|
|
|
|
|
Color: tag.Color,
|
|
|
|
|
CreatedAt: tag.CreatedAt,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return response, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -675,7 +720,7 @@ func (s *ArticleApplicationServiceImpl) ListTags(ctx context.Context) (*response
|
|
|
|
|
s.logger.Error("获取标签列表失败", zap.Error(err))
|
|
|
|
|
return nil, fmt.Errorf("获取标签列表失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 转换为响应对象
|
|
|
|
|
items := make([]responses.TagInfoResponse, len(tags))
|
|
|
|
|
for i, tag := range tags {
|
|
|
|
|
@@ -686,12 +731,12 @@ func (s *ArticleApplicationServiceImpl) ListTags(ctx context.Context) (*response
|
|
|
|
|
CreatedAt: tag.CreatedAt,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
response := &responses.TagListResponse{
|
|
|
|
|
Items: items,
|
|
|
|
|
Total: len(items),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return response, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|