This commit is contained in:
2025-12-06 13:56:00 +08:00
parent 05b6623e75
commit 89367fb2ee
22 changed files with 2186 additions and 74 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,24 @@
// 存储公告的仓储接口
package repositories
import (
"context"
"tyapi-server/internal/domains/article/entities"
"tyapi-server/internal/domains/article/repositories/queries"
"tyapi-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,13 @@
package queries
import "tyapi-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,133 @@
package services
import (
"tyapi-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
}