| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  |  | package entities | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | import ( | 
					
						
							|  |  |  |  | 	"time" | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	"github.com/google/uuid" | 
					
						
							|  |  |  |  | 	"gorm.io/gorm" | 
					
						
							|  |  |  |  | ) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // ArticleStatus 文章状态枚举 | 
					
						
							|  |  |  |  | type ArticleStatus string | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | const ( | 
					
						
							|  |  |  |  | 	ArticleStatusDraft     ArticleStatus = "draft"     // 草稿 | 
					
						
							|  |  |  |  | 	ArticleStatusPublished ArticleStatus = "published" // 已发布 | 
					
						
							|  |  |  |  | 	ArticleStatusArchived  ArticleStatus = "archived"  // 已归档 | 
					
						
							|  |  |  |  | ) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // Article 文章聚合根 | 
					
						
							|  |  |  |  | // 系统的核心内容实体,提供文章的完整生命周期管理 | 
					
						
							|  |  |  |  | // 支持草稿、发布、归档状态,实现Entity接口便于统一管理 | 
					
						
							|  |  |  |  | type Article struct { | 
					
						
							|  |  |  |  | 	// 基础标识 | 
					
						
							|  |  |  |  | 	ID         string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"文章唯一标识"` | 
					
						
							|  |  |  |  | 	Title      string `gorm:"type:varchar(200);not null" json:"title" comment:"文章标题"` | 
					
						
							|  |  |  |  | 	Content    string `gorm:"type:text;not null" json:"content" comment:"文章内容"` | 
					
						
							|  |  |  |  | 	Summary    string `gorm:"type:varchar(500)" json:"summary" comment:"文章摘要"` | 
					
						
							|  |  |  |  | 	CoverImage string `gorm:"type:varchar(500)" json:"cover_image" comment:"封面图片"` | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// 分类 | 
					
						
							|  |  |  |  | 	CategoryID string `gorm:"type:varchar(36)" json:"category_id" comment:"分类ID"` | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// 状态管理 | 
					
						
							|  |  |  |  | 	Status      ArticleStatus `gorm:"type:varchar(20);not null;default:'draft'" json:"status" comment:"文章状态"` | 
					
						
							|  |  |  |  | 	IsFeatured  bool          `gorm:"default:false" json:"is_featured" comment:"是否推荐"` | 
					
						
							|  |  |  |  | 	PublishedAt *time.Time    `json:"published_at" comment:"发布时间"` | 
					
						
							|  |  |  |  | 	ScheduledAt *time.Time    `json:"scheduled_at" comment:"定时发布时间"` | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// 统计信息 | 
					
						
							|  |  |  |  | 	ViewCount int `gorm:"default:0" json:"view_count" comment:"阅读量"` | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// 时间戳字段 | 
					
						
							|  |  |  |  | 	CreatedAt time.Time      `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"` | 
					
						
							|  |  |  |  | 	UpdatedAt time.Time      `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"` | 
					
						
							|  |  |  |  | 	DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"` | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// 关联关系 | 
					
						
							|  |  |  |  | 	Category *Category `gorm:"foreignKey:CategoryID" json:"category,omitempty" comment:"分类信息"` | 
					
						
							|  |  |  |  | 	Tags     []Tag     `gorm:"many2many:article_tag_relations;" json:"tags,omitempty" comment:"标签列表"` | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// 领域事件 (不持久化) | 
					
						
							|  |  |  |  | 	domainEvents []interface{} `gorm:"-" json:"-"` | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // TableName 指定表名 | 
					
						
							|  |  |  |  | func (Article) TableName() string { | 
					
						
							|  |  |  |  | 	return "articles" | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // BeforeCreate GORM钩子:创建前自动生成UUID | 
					
						
							|  |  |  |  | func (a *Article) BeforeCreate(tx *gorm.DB) error { | 
					
						
							|  |  |  |  | 	if a.ID == "" { | 
					
						
							|  |  |  |  | 		a.ID = uuid.New().String() | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	return nil | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // 实现 Entity 接口 - 提供统一的实体管理接口 | 
					
						
							|  |  |  |  | // GetID 获取实体唯一标识 | 
					
						
							|  |  |  |  | func (a *Article) GetID() string { | 
					
						
							|  |  |  |  | 	return a.ID | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // GetCreatedAt 获取创建时间 | 
					
						
							|  |  |  |  | func (a *Article) GetCreatedAt() time.Time { | 
					
						
							|  |  |  |  | 	return a.CreatedAt | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // GetUpdatedAt 获取更新时间 | 
					
						
							|  |  |  |  | func (a *Article) GetUpdatedAt() time.Time { | 
					
						
							|  |  |  |  | 	return a.UpdatedAt | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // Validate 验证文章信息 | 
					
						
							|  |  |  |  | // 检查文章必填字段是否完整,确保数据的有效性 | 
					
						
							|  |  |  |  | func (a *Article) Validate() error { | 
					
						
							|  |  |  |  | 	if a.Title == "" { | 
					
						
							|  |  |  |  | 		return NewValidationError("文章标题不能为空") | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	if a.Content == "" { | 
					
						
							|  |  |  |  | 		return NewValidationError("文章内容不能为空") | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// 验证标题长度 | 
					
						
							|  |  |  |  | 	if len(a.Title) > 200 { | 
					
						
							|  |  |  |  | 		return NewValidationError("文章标题不能超过200个字符") | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// 验证摘要长度 | 
					
						
							|  |  |  |  | 	if a.Summary != "" && len(a.Summary) > 500 { | 
					
						
							|  |  |  |  | 		return NewValidationError("文章摘要不能超过500个字符") | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	return nil | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // Publish 发布文章 | 
					
						
							|  |  |  |  | func (a *Article) Publish() error { | 
					
						
							|  |  |  |  | 	if a.Status == ArticleStatusPublished { | 
					
						
							|  |  |  |  | 		return NewValidationError("文章已经是发布状态") | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	a.Status = ArticleStatusPublished | 
					
						
							|  |  |  |  | 	now := time.Now() | 
					
						
							|  |  |  |  | 	a.PublishedAt = &now | 
					
						
							|  |  |  |  | 	a.ScheduledAt = nil // 清除定时发布时间 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	return nil | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // SchedulePublish 定时发布文章 | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  |  | func (a *Article) SchedulePublish(scheduledTime time.Time) error { | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  |  | 	if a.Status == ArticleStatusPublished { | 
					
						
							|  |  |  |  | 		return NewValidationError("文章已经是发布状态") | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	if scheduledTime.Before(time.Now()) { | 
					
						
							|  |  |  |  | 		return NewValidationError("定时发布时间不能早于当前时间") | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	a.Status = ArticleStatusDraft // 保持草稿状态,等待定时发布 | 
					
						
							|  |  |  |  | 	a.ScheduledAt = &scheduledTime | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	return nil | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // UpdateSchedulePublish 更新定时发布时间 | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  |  | func (a *Article) UpdateSchedulePublish(scheduledTime time.Time) error { | 
					
						
							| 
									
										
										
										
											2025-09-02 16:37:28 +08:00
										 |  |  |  | 	if a.Status == ArticleStatusPublished { | 
					
						
							|  |  |  |  | 		return NewValidationError("文章已经是发布状态") | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	if scheduledTime.Before(time.Now()) { | 
					
						
							|  |  |  |  | 		return NewValidationError("定时发布时间不能早于当前时间") | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	a.ScheduledAt = &scheduledTime | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	return nil | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // CancelSchedulePublish 取消定时发布 | 
					
						
							|  |  |  |  | func (a *Article) CancelSchedulePublish() error { | 
					
						
							|  |  |  |  | 	if a.Status == ArticleStatusPublished { | 
					
						
							|  |  |  |  | 		return NewValidationError("文章已经是发布状态") | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	a.ScheduledAt = nil | 
					
						
							| 
									
										
										
										
											2025-09-01 18:29:59 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	return nil | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // IsScheduled 判断是否已设置定时发布 | 
					
						
							|  |  |  |  | func (a *Article) IsScheduled() bool { | 
					
						
							|  |  |  |  | 	return a.ScheduledAt != nil && a.Status == ArticleStatusDraft | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // GetScheduledTime 获取定时发布时间 | 
					
						
							|  |  |  |  | func (a *Article) GetScheduledTime() *time.Time { | 
					
						
							|  |  |  |  | 	return a.ScheduledAt | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // Archive 归档文章 | 
					
						
							|  |  |  |  | func (a *Article) Archive() error { | 
					
						
							|  |  |  |  | 	if a.Status == ArticleStatusArchived { | 
					
						
							|  |  |  |  | 		return NewValidationError("文章已经是归档状态") | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	a.Status = ArticleStatusArchived | 
					
						
							|  |  |  |  | 	return nil | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // IncrementViewCount 增加阅读量 | 
					
						
							|  |  |  |  | func (a *Article) IncrementViewCount() { | 
					
						
							|  |  |  |  | 	a.ViewCount++ | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // SetFeatured 设置推荐状态 | 
					
						
							|  |  |  |  | func (a *Article) SetFeatured(featured bool) { | 
					
						
							|  |  |  |  | 	a.IsFeatured = featured | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // IsPublished 判断是否已发布 | 
					
						
							|  |  |  |  | func (a *Article) IsPublished() bool { | 
					
						
							|  |  |  |  | 	return a.Status == ArticleStatusPublished | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // IsDraft 判断是否为草稿 | 
					
						
							|  |  |  |  | func (a *Article) IsDraft() bool { | 
					
						
							|  |  |  |  | 	return a.Status == ArticleStatusDraft | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // IsArchived 判断是否已归档 | 
					
						
							|  |  |  |  | func (a *Article) IsArchived() bool { | 
					
						
							|  |  |  |  | 	return a.Status == ArticleStatusArchived | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // CanEdit 判断是否可以编辑 | 
					
						
							|  |  |  |  | func (a *Article) CanEdit() bool { | 
					
						
							|  |  |  |  | 	return a.Status == ArticleStatusDraft | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // CanPublish 判断是否可以发布 | 
					
						
							|  |  |  |  | func (a *Article) CanPublish() bool { | 
					
						
							|  |  |  |  | 	return a.Status == ArticleStatusDraft | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // CanArchive 判断是否可以归档 | 
					
						
							|  |  |  |  | func (a *Article) CanArchive() bool { | 
					
						
							|  |  |  |  | 	return a.Status == ArticleStatusPublished | 
					
						
							|  |  |  |  | } |