This commit is contained in:
2026-04-21 22:36:48 +08:00
commit 488c695fdf
748 changed files with 266838 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// AnnouncementStatus 公告状态枚举
type AnnouncementStatus string
const (
AnnouncementStatusDraft AnnouncementStatus = "draft" // 草稿
AnnouncementStatusPublished AnnouncementStatus = "published" // 已发布
AnnouncementStatusArchived AnnouncementStatus = "archived" // 已归档
)
// Announcement 公告聚合根
// 用于对系统公告进行管理,支持发布、撤回、定时发布等功能
type Announcement struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"公告唯一标识"`
Title string `gorm:"type:varchar(200);not null;index" json:"title" comment:"公告标题"`
Content string `gorm:"type:text;not null" json:"content" comment:"公告内容"`
Status AnnouncementStatus `gorm:"type:varchar(20);not null;default:'draft';index" json:"status" comment:"公告状态"`
ScheduledAt *time.Time `gorm:"index" json:"scheduled_at" comment:"定时发布时间"`
CreatedAt time.Time `gorm:"autoCreateTime;index" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
}
// TableName 指定表名
func (Announcement) TableName() string {
return "announcements"
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (a *Announcement) BeforeCreate(tx *gorm.DB) error {
if a.ID == "" {
a.ID = uuid.New().String()
}
return nil
}
// 实现 Entity 接口 - 提供统一的实体管理接口
// GetID 获取实体唯一标识
func (a *Announcement) GetID() string {
return a.ID
}
// GetCreatedAt 获取创建时间
func (a *Announcement) GetCreatedAt() time.Time {
return a.CreatedAt
}
// GetUpdatedAt 获取更新时间
func (a *Announcement) GetUpdatedAt() time.Time {
return a.UpdatedAt
}
// 验证公告信息
func (a *Announcement) Validate() error {
if a.Title == "" {
return NewValidationError("公告标题不能为空")
}
if a.Content == "" {
return NewValidationError("公告内容不能为空")
}
return nil
}
// 发布公告
func (a *Announcement) Publish() error {
if a.Status == AnnouncementStatusPublished {
return NewValidationError("公告已经是发布状态")
}
a.Status = AnnouncementStatusPublished
now := time.Now()
a.CreatedAt = now
return nil
}
// 撤回公告
func (a *Announcement) Withdraw() error {
if a.Status == AnnouncementStatusDraft {
return NewValidationError("公告已经是草稿状态")
}
a.Status = AnnouncementStatusDraft
now := time.Now()
a.CreatedAt = now
return nil
}
// 定时发布公告
func (a *Announcement) SchedulePublish(scheduledTime time.Time) error {
if a.Status == AnnouncementStatusPublished {
return NewValidationError("公告已经是发布状态")
}
a.Status = AnnouncementStatusDraft // 保持草稿状态,等待定时发布
a.ScheduledAt = &scheduledTime
return nil
}
// 更新定时发布时间
func (a *Announcement) UpdateSchedulePublish(scheduledTime time.Time) error {
if a.Status == AnnouncementStatusPublished {
return NewValidationError("公告已经是发布状态")
}
if scheduledTime.Before(time.Now()) {
return NewValidationError("定时发布时间不能早于当前时间")
}
a.ScheduledAt = &scheduledTime
return nil
}
// CancelSchedulePublish 取消定时发布
func (a *Announcement) CancelSchedulePublish() error {
if a.Status == AnnouncementStatusPublished {
return NewValidationError("公告已经是发布状态")
}
a.ScheduledAt = nil
return nil
}
// IsScheduled 判断是否已设置定时发布
func (a *Announcement) IsScheduled() bool {
return a.ScheduledAt != nil && a.Status == AnnouncementStatusDraft
}
// GetScheduledTime 获取定时发布时间
func (a *Announcement) GetScheduledTime() *time.Time {
return a.ScheduledAt
}

View File

@@ -0,0 +1,221 @@
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 定时发布文章
func (a *Article) SchedulePublish(scheduledTime time.Time) error {
if a.Status == ArticleStatusPublished {
return NewValidationError("文章已经是发布状态")
}
if scheduledTime.Before(time.Now()) {
return NewValidationError("定时发布时间不能早于当前时间")
}
a.Status = ArticleStatusDraft // 保持草稿状态,等待定时发布
a.ScheduledAt = &scheduledTime
return nil
}
// UpdateSchedulePublish 更新定时发布时间
func (a *Article) UpdateSchedulePublish(scheduledTime time.Time) error {
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
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
}

View File

@@ -0,0 +1,78 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// Category 文章分类实体
// 用于对文章进行分类管理,支持层级结构和排序
type Category struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"分类唯一标识"`
Name string `gorm:"type:varchar(100);not null" json:"name" comment:"分类名称"`
Description string `gorm:"type:text" json:"description" comment:"分类描述"`
SortOrder int `gorm:"default:0" json:"sort_order" 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:"软删除时间"`
// 关联关系
Articles []Article `gorm:"foreignKey:CategoryID" json:"articles,omitempty" comment:"分类下的文章"`
// 领域事件 (不持久化)
domainEvents []interface{} `gorm:"-" json:"-"`
}
// TableName 指定表名
func (Category) TableName() string {
return "article_categories"
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (c *Category) BeforeCreate(tx *gorm.DB) error {
if c.ID == "" {
c.ID = uuid.New().String()
}
return nil
}
// 实现 Entity 接口 - 提供统一的实体管理接口
// GetID 获取实体唯一标识
func (c *Category) GetID() string {
return c.ID
}
// GetCreatedAt 获取创建时间
func (c *Category) GetCreatedAt() time.Time {
return c.CreatedAt
}
// GetUpdatedAt 获取更新时间
func (c *Category) GetUpdatedAt() time.Time {
return c.UpdatedAt
}
// Validate 验证分类信息
// 检查分类必填字段是否完整,确保数据的有效性
func (c *Category) Validate() error {
if c.Name == "" {
return NewValidationError("分类名称不能为空")
}
// 验证名称长度
if len(c.Name) > 100 {
return NewValidationError("分类名称不能超过100个字符")
}
return nil
}
// SetSortOrder 设置排序
func (c *Category) SetSortOrder(order int) {
c.SortOrder = order
}

View File

@@ -0,0 +1,21 @@
package entities
// ValidationError 验证错误
type ValidationError struct {
Message string
}
func (e *ValidationError) Error() string {
return e.Message
}
// NewValidationError 创建验证错误
func NewValidationError(message string) *ValidationError {
return &ValidationError{Message: message}
}
// IsValidationError 判断是否为验证错误
func IsValidationError(err error) bool {
_, ok := err.(*ValidationError)
return ok
}

View File

@@ -0,0 +1,113 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// TaskStatus 任务状态枚举
type TaskStatus string
const (
TaskStatusPending TaskStatus = "pending" // 等待执行
TaskStatusRunning TaskStatus = "running" // 正在执行
TaskStatusCompleted TaskStatus = "completed" // 已完成
TaskStatusFailed TaskStatus = "failed" // 执行失败
TaskStatusCancelled TaskStatus = "cancelled" // 已取消
)
// ScheduledTask 定时任务状态管理实体
type ScheduledTask struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"任务唯一标识"`
TaskID string `gorm:"type:varchar(100);not null;uniqueIndex" json:"task_id" comment:"Asynq任务ID"`
TaskType string `gorm:"type:varchar(50);not null" json:"task_type" comment:"任务类型"`
// 关联信息
ArticleID string `gorm:"type:varchar(36);not null;index" json:"article_id" comment:"关联的文章ID"`
// 任务状态
Status TaskStatus `gorm:"type:varchar(20);not null;default:'pending'" json:"status" comment:"任务状态"`
// 时间信息
ScheduledAt time.Time `gorm:"not null" json:"scheduled_at" comment:"计划执行时间"`
StartedAt *time.Time `json:"started_at" comment:"开始执行时间"`
CompletedAt *time.Time `json:"completed_at" comment:"完成时间"`
// 执行结果
Error string `gorm:"type:text" json:"error" comment:"错误信息"`
RetryCount int `gorm:"default:0" json:"retry_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:"软删除时间"`
// 关联关系
Article *Article `gorm:"foreignKey:ArticleID" json:"article,omitempty" comment:"关联的文章"`
}
// TableName 指定表名
func (ScheduledTask) TableName() string {
return "scheduled_tasks"
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (st *ScheduledTask) BeforeCreate(tx *gorm.DB) error {
if st.ID == "" {
st.ID = uuid.New().String()
}
return nil
}
// MarkAsRunning 标记任务为正在执行
func (st *ScheduledTask) MarkAsRunning() {
st.Status = TaskStatusRunning
now := time.Now()
st.StartedAt = &now
}
// MarkAsCompleted 标记任务为已完成
func (st *ScheduledTask) MarkAsCompleted() {
st.Status = TaskStatusCompleted
now := time.Now()
st.CompletedAt = &now
}
// MarkAsFailed 标记任务为执行失败
func (st *ScheduledTask) MarkAsFailed(errorMsg string) {
st.Status = TaskStatusFailed
now := time.Now()
st.CompletedAt = &now
st.Error = errorMsg
st.RetryCount++
}
// MarkAsCancelled 标记任务为已取消
func (st *ScheduledTask) MarkAsCancelled() {
st.Status = TaskStatusCancelled
now := time.Now()
st.CompletedAt = &now
}
// IsActive 判断任务是否处于活动状态
func (st *ScheduledTask) IsActive() bool {
return st.Status == TaskStatusPending || st.Status == TaskStatusRunning
}
// IsCancelled 判断任务是否已取消
func (st *ScheduledTask) IsCancelled() bool {
return st.Status == TaskStatusCancelled
}
// IsCompleted 判断任务是否已完成
func (st *ScheduledTask) IsCompleted() bool {
return st.Status == TaskStatusCompleted
}
// IsFailed 判断任务是否执行失败
func (st *ScheduledTask) IsFailed() bool {
return st.Status == TaskStatusFailed
}

View File

@@ -0,0 +1,102 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// Tag 文章标签实体
// 用于对文章进行标签化管理,支持颜色配置
type Tag struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"标签唯一标识"`
Name string `gorm:"type:varchar(50);not null" json:"name" comment:"标签名称"`
Color string `gorm:"type:varchar(20);default:'#1890ff'" json:"color" 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:"软删除时间"`
// 关联关系
Articles []Article `gorm:"many2many:article_tag_relations;" json:"articles,omitempty" comment:"标签下的文章"`
// 领域事件 (不持久化)
domainEvents []interface{} `gorm:"-" json:"-"`
}
// TableName 指定表名
func (Tag) TableName() string {
return "article_tags"
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (t *Tag) BeforeCreate(tx *gorm.DB) error {
if t.ID == "" {
t.ID = uuid.New().String()
}
return nil
}
// 实现 Entity 接口 - 提供统一的实体管理接口
// GetID 获取实体唯一标识
func (t *Tag) GetID() string {
return t.ID
}
// GetCreatedAt 获取创建时间
func (t *Tag) GetCreatedAt() time.Time {
return t.CreatedAt
}
// GetUpdatedAt 获取更新时间
func (t *Tag) GetUpdatedAt() time.Time {
return t.UpdatedAt
}
// Validate 验证标签信息
// 检查标签必填字段是否完整,确保数据的有效性
func (t *Tag) Validate() error {
if t.Name == "" {
return NewValidationError("标签名称不能为空")
}
// 验证名称长度
if len(t.Name) > 50 {
return NewValidationError("标签名称不能超过50个字符")
}
// 验证颜色格式
if t.Color != "" && !isValidColor(t.Color) {
return NewValidationError("标签颜色格式无效")
}
return nil
}
// SetColor 设置标签颜色
func (t *Tag) SetColor(color string) error {
if color != "" && !isValidColor(color) {
return NewValidationError("标签颜色格式无效")
}
t.Color = color
return nil
}
// isValidColor 验证颜色格式
func isValidColor(color string) bool {
// 简单的颜色格式验证,支持 #RRGGBB 格式
if len(color) == 7 && color[0] == '#' {
for i := 1; i < 7; i++ {
if !((color[i] >= '0' && color[i] <= '9') ||
(color[i] >= 'a' && color[i] <= 'f') ||
(color[i] >= 'A' && color[i] <= 'F')) {
return false
}
}
return true
}
return false
}

View File

@@ -0,0 +1,24 @@
// 存储公告的仓储接口
package repositories
import (
"context"
"hyapi-server/internal/domains/article/entities"
"hyapi-server/internal/domains/article/repositories/queries"
"hyapi-server/internal/shared/interfaces"
)
// AnnouncementRepository 公告仓储接口
type AnnouncementRepository interface {
interfaces.Repository[entities.Announcement]
// 自定义查询方法
FindByStatus(ctx context.Context, status entities.AnnouncementStatus) ([]*entities.Announcement, error)
FindScheduled(ctx context.Context) ([]*entities.Announcement, error)
ListAnnouncements(ctx context.Context, query *queries.ListAnnouncementQuery) ([]*entities.Announcement, int64, error)
// 统计方法
CountByStatus(ctx context.Context, status entities.AnnouncementStatus) (int64, error)
// 更新统计信息
UpdateStatistics(ctx context.Context, announcementID string) error
}

View File

@@ -0,0 +1,29 @@
package repositories
import (
"context"
"hyapi-server/internal/domains/article/entities"
"hyapi-server/internal/domains/article/repositories/queries"
"hyapi-server/internal/shared/interfaces"
)
// ArticleRepository 文章仓储接口
type ArticleRepository interface {
interfaces.Repository[entities.Article]
// 自定义查询方法
FindByAuthorID(ctx context.Context, authorID string) ([]*entities.Article, error)
FindByCategoryID(ctx context.Context, categoryID string) ([]*entities.Article, error)
FindByStatus(ctx context.Context, status entities.ArticleStatus) ([]*entities.Article, error)
FindFeatured(ctx context.Context) ([]*entities.Article, error)
Search(ctx context.Context, query *queries.SearchArticleQuery) ([]*entities.Article, int64, error)
ListArticles(ctx context.Context, query *queries.ListArticleQuery) ([]*entities.Article, int64, error)
ListArticlesForAdmin(ctx context.Context, query *queries.ListArticleQuery) ([]*entities.Article, int64, error)
// 统计方法
CountByCategoryID(ctx context.Context, categoryID string) (int64, error)
CountByStatus(ctx context.Context, status entities.ArticleStatus) (int64, error)
// 更新统计信息
IncrementViewCount(ctx context.Context, articleID string) error
}

View File

@@ -0,0 +1,19 @@
package repositories
import (
"context"
"hyapi-server/internal/domains/article/entities"
"hyapi-server/internal/shared/interfaces"
)
// CategoryRepository 分类仓储接口
type CategoryRepository interface {
interfaces.Repository[entities.Category]
// 自定义查询方法
FindActive(ctx context.Context) ([]*entities.Category, error)
FindBySortOrder(ctx context.Context) ([]*entities.Category, error)
// 统计方法
CountActive(ctx context.Context) (int64, error)
}

View File

@@ -0,0 +1,13 @@
package queries
import "hyapi-server/internal/domains/article/entities"
// ListAnnouncementQuery 公告列表查询
type ListAnnouncementQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Status entities.AnnouncementStatus `json:"status"`
Title string `json:"title"`
OrderBy string `json:"order_by"`
OrderDir string `json:"order_dir"`
}

View File

@@ -0,0 +1,48 @@
package queries
import "hyapi-server/internal/domains/article/entities"
// ListArticleQuery 文章列表查询
type ListArticleQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Status entities.ArticleStatus `json:"status"`
CategoryID string `json:"category_id"`
TagID string `json:"tag_id"`
Title string `json:"title"`
Summary string `json:"summary"`
IsFeatured *bool `json:"is_featured"`
OrderBy string `json:"order_by"`
OrderDir string `json:"order_dir"`
}
// SearchArticleQuery 文章搜索查询
type SearchArticleQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Keyword string `json:"keyword"`
CategoryID string `json:"category_id"`
AuthorID string `json:"author_id"`
Status entities.ArticleStatus `json:"status"`
OrderBy string `json:"order_by"`
OrderDir string `json:"order_dir"`
}
// GetArticleQuery 获取文章详情查询
type GetArticleQuery struct {
ID string `json:"id"`
}
// GetArticlesByAuthorQuery 获取作者文章查询
type GetArticlesByAuthorQuery struct {
AuthorID string `json:"author_id"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
// GetArticlesByCategoryQuery 获取分类文章查询
type GetArticlesByCategoryQuery struct {
CategoryID string `json:"category_id"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}

View File

@@ -0,0 +1,33 @@
package repositories
import (
"context"
"hyapi-server/internal/domains/article/entities"
)
// ScheduledTaskRepository 定时任务仓储接口
type ScheduledTaskRepository interface {
// Create 创建定时任务记录
Create(ctx context.Context, task entities.ScheduledTask) (entities.ScheduledTask, error)
// GetByTaskID 根据Asynq任务ID获取任务记录
GetByTaskID(ctx context.Context, taskID string) (entities.ScheduledTask, error)
// GetByArticleID 根据文章ID获取任务记录
GetByArticleID(ctx context.Context, articleID string) (entities.ScheduledTask, error)
// Update 更新任务记录
Update(ctx context.Context, task entities.ScheduledTask) error
// Delete 删除任务记录
Delete(ctx context.Context, taskID string) error
// MarkAsCancelled 标记任务为已取消
MarkAsCancelled(ctx context.Context, taskID string) error
// GetActiveTasks 获取活动状态的任务列表
GetActiveTasks(ctx context.Context) ([]entities.ScheduledTask, error)
// GetExpiredTasks 获取过期的任务列表
GetExpiredTasks(ctx context.Context) ([]entities.ScheduledTask, error)
}

View File

@@ -0,0 +1,21 @@
package repositories
import (
"context"
"hyapi-server/internal/domains/article/entities"
"hyapi-server/internal/shared/interfaces"
)
// TagRepository 标签仓储接口
type TagRepository interface {
interfaces.Repository[entities.Tag]
// 自定义查询方法
FindByArticleID(ctx context.Context, articleID string) ([]*entities.Tag, error)
FindByName(ctx context.Context, name string) (*entities.Tag, error)
// 关联方法
AddTagToArticle(ctx context.Context, articleID string, tagID string) error
RemoveTagFromArticle(ctx context.Context, articleID string, tagID string) error
GetArticleTags(ctx context.Context, articleID string) ([]*entities.Tag, error)
}

View File

@@ -0,0 +1,133 @@
package services
import (
"hyapi-server/internal/domains/article/entities"
)
// AnnouncementService 公告领域服务
// 处理公告相关的业务逻辑,包括验证、状态管理等
type AnnouncementService struct{}
// NewAnnouncementService 创建公告领域服务
func NewAnnouncementService() *AnnouncementService {
return &AnnouncementService{}
}
// ValidateAnnouncement 验证公告
// 检查公告是否符合业务规则
func (s *AnnouncementService) ValidateAnnouncement(announcement *entities.Announcement) error {
// 1. 基础验证
if err := announcement.Validate(); err != nil {
return err
}
// 2. 业务规则验证
// 标题不能包含敏感词
if s.containsSensitiveWords(announcement.Title) {
return entities.NewValidationError("公告标题包含敏感词")
}
// 内容不能包含敏感词
if s.containsSensitiveWords(announcement.Content) {
return entities.NewValidationError("公告内容包含敏感词")
}
// 标题长度验证
if len(announcement.Title) > 200 {
return entities.NewValidationError("公告标题不能超过200个字符")
}
return nil
}
// CanPublish 检查是否可以发布
func (s *AnnouncementService) CanPublish(announcement *entities.Announcement) error {
if announcement.Status == entities.AnnouncementStatusPublished {
return entities.NewValidationError("公告已经是发布状态")
}
if announcement.Status == entities.AnnouncementStatusArchived {
return entities.NewValidationError("已归档的公告不能发布")
}
// 检查必填字段
if announcement.Title == "" {
return entities.NewValidationError("公告标题不能为空")
}
if announcement.Content == "" {
return entities.NewValidationError("公告内容不能为空")
}
return nil
}
// CanEdit 检查是否可以编辑
func (s *AnnouncementService) CanEdit(announcement *entities.Announcement) error {
if announcement.Status == entities.AnnouncementStatusPublished {
return entities.NewValidationError("已发布的公告不能编辑,请先撤回")
}
if announcement.Status == entities.AnnouncementStatusArchived {
return entities.NewValidationError("已归档的公告不能编辑")
}
return nil
}
// CanArchive 检查是否可以归档
func (s *AnnouncementService) CanArchive(announcement *entities.Announcement) error {
if announcement.Status != entities.AnnouncementStatusPublished {
return entities.NewValidationError("只有已发布的公告才能归档")
}
return nil
}
// CanWithdraw 检查是否可以撤回
func (s *AnnouncementService) CanWithdraw(announcement *entities.Announcement) error {
if announcement.Status != entities.AnnouncementStatusPublished {
return entities.NewValidationError("只有已发布的公告才能撤回")
}
return nil
}
// CanSchedulePublish 检查是否可以定时发布
func (s *AnnouncementService) CanSchedulePublish(announcement *entities.Announcement, scheduledTime interface{}) error {
if announcement.Status == entities.AnnouncementStatusPublished {
return entities.NewValidationError("已发布的公告不能设置定时发布")
}
if announcement.Status == entities.AnnouncementStatusArchived {
return entities.NewValidationError("已归档的公告不能设置定时发布")
}
return nil
}
// containsSensitiveWords 检查是否包含敏感词
func (s *AnnouncementService) containsSensitiveWords(text string) bool {
// TODO: 实现敏感词检查逻辑
// 这里可以集成敏感词库或调用外部服务
sensitiveWords := []string{
"敏感词1",
"敏感词2",
"敏感词3",
}
for _, word := range sensitiveWords {
if len(word) > 0 && len(text) > 0 {
// 简单的字符串包含检查
// 实际项目中应该使用更复杂的算法
if len(text) >= len(word) {
for i := 0; i <= len(text)-len(word); i++ {
if text[i:i+len(word)] == word {
return true
}
}
}
}
}
return false
}

View File

@@ -0,0 +1,94 @@
package services
import (
"hyapi-server/internal/domains/article/entities"
)
// ArticleService 文章领域服务
// 处理文章相关的业务逻辑,包括验证、状态管理等
type ArticleService struct{}
// NewArticleService 创建文章领域服务
func NewArticleService() *ArticleService {
return &ArticleService{}
}
// ValidateArticle 验证文章
// 检查文章是否符合业务规则
func (s *ArticleService) ValidateArticle(article *entities.Article) error {
// 1. 基础验证
if err := article.Validate(); err != nil {
return err
}
// 2. 业务规则验证
// 标题不能包含敏感词
if s.containsSensitiveWords(article.Title) {
return entities.NewValidationError("文章标题包含敏感词")
}
// 内容不能包含敏感词
if s.containsSensitiveWords(article.Content) {
return entities.NewValidationError("文章内容包含敏感词")
}
// 摘要长度不能超过内容长度
if article.Summary != "" && len(article.Summary) >= len(article.Content) {
return entities.NewValidationError("文章摘要不能超过内容长度")
}
return nil
}
// CanPublish 检查是否可以发布
func (s *ArticleService) CanPublish(article *entities.Article) error {
if !article.CanPublish() {
return entities.NewValidationError("文章状态不允许发布")
}
// 检查必填字段
if article.Title == "" {
return entities.NewValidationError("文章标题不能为空")
}
if article.Content == "" {
return entities.NewValidationError("文章内容不能为空")
}
return nil
}
// CanEdit 检查是否可以编辑
func (s *ArticleService) CanEdit(article *entities.Article) error {
if !article.CanEdit() {
return entities.NewValidationError("文章状态不允许编辑")
}
return nil
}
// containsSensitiveWords 检查是否包含敏感词
func (s *ArticleService) containsSensitiveWords(text string) bool {
// TODO: 实现敏感词检查逻辑
// 这里可以集成敏感词库或调用外部服务
sensitiveWords := []string{
"敏感词1",
"敏感词2",
"敏感词3",
}
for _, word := range sensitiveWords {
if len(word) > 0 && len(text) > 0 {
// 简单的字符串包含检查
// 实际项目中应该使用更复杂的算法
if len(text) >= len(word) {
for i := 0; i <= len(text)-len(word); i++ {
if text[i:i+len(word)] == word {
return true
}
}
}
}
}
return false
}