Files
tyapi-server/internal/application/article/article_application_service_impl.go
2025-09-02 16:37:28 +08:00

773 lines
24 KiB
Go

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"
"tyapi-server/internal/infrastructure/task"
"tyapi-server/internal/shared/interfaces"
"go.uber.org/zap"
)
// ArticleApplicationServiceImpl 文章应用服务实现
type ArticleApplicationServiceImpl struct {
articleRepo repositories.ArticleRepository
categoryRepo repositories.CategoryRepository
tagRepo repositories.TagRepository
articleService *services.ArticleService
asynqClient *task.AsynqClient
logger *zap.Logger
}
// NewArticleApplicationService 创建文章应用服务
func NewArticleApplicationService(
articleRepo repositories.ArticleRepository,
categoryRepo repositories.CategoryRepository,
tagRepo repositories.TagRepository,
articleService *services.ArticleService,
asynqClient *task.AsynqClient,
logger *zap.Logger,
) ArticleApplicationService {
return &ArticleApplicationServiceImpl{
articleRepo: articleRepo,
categoryRepo: categoryRepo,
tagRepo: tagRepo,
articleService: articleService,
asynqClient: asynqClient,
logger: logger,
}
}
// 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)
}
// 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,
}
// 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 {
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), 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)
}
// 2. 检查是否可以编辑
if !article.CanEdit() {
return fmt.Errorf("文章状态不允许编辑")
}
// 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
// 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. 处理标签关联
// 先清除现有标签
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
}
// 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)
}
// 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
}
// 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)
}
// 2. 转换为响应对象
response := responses.FromArticleEntity(&article)
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,
}
// 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
}
// 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,
}
// 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. 获取文章
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.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
}
// 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)
}
// 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 !article.IsScheduled() {
return fmt.Errorf("文章未设置定时发布")
}
// 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)
}
s.logger.Info("取消定时发布成功", zap.String("id", article.ID))
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)
}
// 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
}
// 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)
}
// 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. 增加阅读量
if err := s.articleRepo.IncrementViewCount(ctx, articleID); err != nil {
s.logger.Error("增加阅读量失败", zap.String("id", articleID), zap.Error(err))
return fmt.Errorf("记录阅读失败: %w", err)
}
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)
}
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,
DraftArticles: draftArticles,
ArchivedArticles: archivedArticles,
TotalViews: 0, // TODO: 实现总阅读量统计
}
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)
}
// 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
}
// 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)
}
// 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
}
// 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)
}
// 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
}
// 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)
}
// 2. 转换为响应对象
response := &responses.CategoryInfoResponse{
ID: category.ID,
Name: category.Name,
Description: category.Description,
SortOrder: category.SortOrder,
CreatedAt: category.CreatedAt,
}
return response, nil
}
// ListCategories 获取分类列表
func (s *ArticleApplicationServiceImpl) ListCategories(ctx context.Context) (*responses.CategoryListResponse, error) {
// 1. 获取分类列表
categories, err := s.categoryRepo.List(ctx, interfaces.ListOptions{})
if err != nil {
s.logger.Error("获取分类列表失败", zap.Error(err))
return nil, fmt.Errorf("获取分类列表失败: %w", err)
}
// 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,
}
}
response := &responses.CategoryListResponse{
Items: items,
Total: len(items),
}
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)
}
// 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
}
// 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)
}
// 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
}
// 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)
}
// 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
}
// 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)
}
// 2. 转换为响应对象
response := &responses.TagInfoResponse{
ID: tag.ID,
Name: tag.Name,
Color: tag.Color,
CreatedAt: tag.CreatedAt,
}
return response, nil
}
// ListTags 获取标签列表
func (s *ArticleApplicationServiceImpl) ListTags(ctx context.Context) (*responses.TagListResponse, error) {
// 1. 获取标签列表
tags, err := s.tagRepo.List(ctx, interfaces.ListOptions{})
if err != nil {
s.logger.Error("获取标签列表失败", zap.Error(err))
return nil, fmt.Errorf("获取标签列表失败: %w", err)
}
// 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,
}
}
response := &responses.TagListResponse{
Items: items,
Total: len(items),
}
return response, nil
}
// ==================== 验证方法 ====================
// 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
}