This commit is contained in:
137
internal/domains/article/entities/announcement.go
Normal file
137
internal/domains/article/entities/announcement.go
Normal 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
|
||||
}
|
||||
24
internal/domains/article/repositories/announcement.go
Normal file
24
internal/domains/article/repositories/announcement.go
Normal 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
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
133
internal/domains/article/services/announcement_service.go
Normal file
133
internal/domains/article/services/announcement_service.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user