485 lines
17 KiB
Go
485 lines
17 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"
|
||
task_entities "tyapi-server/internal/infrastructure/task/entities"
|
||
task_interfaces "tyapi-server/internal/infrastructure/task/interfaces"
|
||
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// AnnouncementApplicationServiceImpl 公告应用服务实现
|
||
type AnnouncementApplicationServiceImpl struct {
|
||
announcementRepo repositories.AnnouncementRepository
|
||
announcementService *services.AnnouncementService
|
||
taskManager task_interfaces.TaskManager
|
||
logger *zap.Logger
|
||
}
|
||
|
||
// NewAnnouncementApplicationService 创建公告应用服务
|
||
func NewAnnouncementApplicationService(
|
||
announcementRepo repositories.AnnouncementRepository,
|
||
announcementService *services.AnnouncementService,
|
||
taskManager task_interfaces.TaskManager,
|
||
logger *zap.Logger,
|
||
) AnnouncementApplicationService {
|
||
return &AnnouncementApplicationServiceImpl{
|
||
announcementRepo: announcementRepo,
|
||
announcementService: announcementService,
|
||
taskManager: taskManager,
|
||
logger: logger,
|
||
}
|
||
}
|
||
|
||
// CreateAnnouncement 创建公告
|
||
func (s *AnnouncementApplicationServiceImpl) CreateAnnouncement(ctx context.Context, cmd *commands.CreateAnnouncementCommand) error {
|
||
// 1. 创建公告实体
|
||
announcement := &entities.Announcement{
|
||
Title: cmd.Title,
|
||
Content: cmd.Content,
|
||
Status: entities.AnnouncementStatusDraft,
|
||
}
|
||
|
||
// 2. 调用领域服务验证
|
||
if err := s.announcementService.ValidateAnnouncement(announcement); err != nil {
|
||
return fmt.Errorf("业务验证失败: %w", err)
|
||
}
|
||
|
||
// 3. 保存公告
|
||
_, err := s.announcementRepo.Create(ctx, *announcement)
|
||
if err != nil {
|
||
s.logger.Error("创建公告失败", zap.Error(err))
|
||
return fmt.Errorf("创建公告失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("创建公告成功", zap.String("id", announcement.ID), zap.String("title", announcement.Title))
|
||
return nil
|
||
}
|
||
|
||
// UpdateAnnouncement 更新公告
|
||
func (s *AnnouncementApplicationServiceImpl) UpdateAnnouncement(ctx context.Context, cmd *commands.UpdateAnnouncementCommand) error {
|
||
// 1. 获取原公告
|
||
announcement, err := s.announcementRepo.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.announcementService.CanEdit(&announcement); err != nil {
|
||
return fmt.Errorf("公告状态不允许编辑: %w", err)
|
||
}
|
||
|
||
// 3. 更新字段
|
||
if cmd.Title != "" {
|
||
announcement.Title = cmd.Title
|
||
}
|
||
if cmd.Content != "" {
|
||
announcement.Content = cmd.Content
|
||
}
|
||
|
||
// 4. 验证更新后的公告
|
||
if err := s.announcementService.ValidateAnnouncement(&announcement); err != nil {
|
||
return fmt.Errorf("业务验证失败: %w", err)
|
||
}
|
||
|
||
// 5. 保存更新
|
||
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||
return fmt.Errorf("更新公告失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("更新公告成功", zap.String("id", announcement.ID))
|
||
return nil
|
||
}
|
||
|
||
// DeleteAnnouncement 删除公告
|
||
func (s *AnnouncementApplicationServiceImpl) DeleteAnnouncement(ctx context.Context, cmd *commands.DeleteAnnouncementCommand) error {
|
||
// 1. 检查公告是否存在
|
||
_, err := s.announcementRepo.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.announcementRepo.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
|
||
}
|
||
|
||
// GetAnnouncementByID 获取公告详情
|
||
func (s *AnnouncementApplicationServiceImpl) GetAnnouncementByID(ctx context.Context, query *appQueries.GetAnnouncementQuery) (*responses.AnnouncementInfoResponse, error) {
|
||
// 1. 获取公告
|
||
announcement, err := s.announcementRepo.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.FromAnnouncementEntity(&announcement)
|
||
|
||
return response, nil
|
||
}
|
||
|
||
// ListAnnouncements 获取公告列表
|
||
func (s *AnnouncementApplicationServiceImpl) ListAnnouncements(ctx context.Context, query *appQueries.ListAnnouncementQuery) (*responses.AnnouncementListResponse, error) {
|
||
// 1. 构建仓储查询
|
||
repoQuery := &repoQueries.ListAnnouncementQuery{
|
||
Page: query.Page,
|
||
PageSize: query.PageSize,
|
||
Status: query.Status,
|
||
Title: query.Title,
|
||
OrderBy: query.OrderBy,
|
||
OrderDir: query.OrderDir,
|
||
}
|
||
|
||
// 2. 调用仓储
|
||
announcements, total, err := s.announcementRepo.ListAnnouncements(ctx, repoQuery)
|
||
if err != nil {
|
||
s.logger.Error("获取公告列表失败", zap.Error(err))
|
||
return nil, fmt.Errorf("获取公告列表失败: %w", err)
|
||
}
|
||
|
||
// 3. 转换为响应对象
|
||
items := responses.FromAnnouncementEntityList(announcements)
|
||
|
||
response := &responses.AnnouncementListResponse{
|
||
Total: total,
|
||
Page: query.Page,
|
||
Size: query.PageSize,
|
||
Items: items,
|
||
}
|
||
|
||
s.logger.Info("获取公告列表成功", zap.Int64("total", total))
|
||
return response, nil
|
||
}
|
||
|
||
// PublishAnnouncement 发布公告
|
||
func (s *AnnouncementApplicationServiceImpl) PublishAnnouncement(ctx context.Context, cmd *commands.PublishAnnouncementCommand) error {
|
||
// 1. 获取公告
|
||
announcement, err := s.announcementRepo.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.announcementService.CanPublish(&announcement); err != nil {
|
||
return fmt.Errorf("无法发布公告: %w", err)
|
||
}
|
||
|
||
// 3. 发布公告
|
||
if err := announcement.Publish(); err != nil {
|
||
return fmt.Errorf("发布公告失败: %w", err)
|
||
}
|
||
|
||
// 4. 保存更新
|
||
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||
return fmt.Errorf("发布公告失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("发布公告成功", zap.String("id", announcement.ID))
|
||
return nil
|
||
}
|
||
|
||
// PublishAnnouncementByID 通过ID发布公告 (用于定时任务)
|
||
func (s *AnnouncementApplicationServiceImpl) PublishAnnouncementByID(ctx context.Context, announcementID string) error {
|
||
// 1. 获取公告
|
||
announcement, err := s.announcementRepo.GetByID(ctx, announcementID)
|
||
if err != nil {
|
||
s.logger.Error("获取公告失败", zap.String("id", announcementID), zap.Error(err))
|
||
return fmt.Errorf("公告不存在: %w", err)
|
||
}
|
||
|
||
// 2. 检查是否已取消定时发布
|
||
if !announcement.IsScheduled() {
|
||
s.logger.Info("公告定时发布已取消,跳过执行",
|
||
zap.String("id", announcementID),
|
||
zap.String("status", string(announcement.Status)))
|
||
return nil // 静默返回,不报错
|
||
}
|
||
|
||
// 3. 检查定时发布时间是否匹配
|
||
if announcement.ScheduledAt == nil {
|
||
s.logger.Info("公告没有定时发布时间,跳过执行",
|
||
zap.String("id", announcementID))
|
||
return nil
|
||
}
|
||
|
||
// 4. 发布公告
|
||
if err := announcement.Publish(); err != nil {
|
||
return fmt.Errorf("发布公告失败: %w", err)
|
||
}
|
||
|
||
// 5. 保存更新
|
||
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||
return fmt.Errorf("发布公告失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("定时发布公告成功", zap.String("id", announcement.ID))
|
||
return nil
|
||
}
|
||
|
||
// WithdrawAnnouncement 撤回公告
|
||
func (s *AnnouncementApplicationServiceImpl) WithdrawAnnouncement(ctx context.Context, cmd *commands.WithdrawAnnouncementCommand) error {
|
||
// 1. 获取公告
|
||
announcement, err := s.announcementRepo.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.announcementService.CanWithdraw(&announcement); err != nil {
|
||
return fmt.Errorf("无法撤回公告: %w", err)
|
||
}
|
||
|
||
// 3. 撤回公告
|
||
if err := announcement.Withdraw(); err != nil {
|
||
return fmt.Errorf("撤回公告失败: %w", err)
|
||
}
|
||
|
||
// 4. 保存更新
|
||
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||
return fmt.Errorf("撤回公告失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("撤回公告成功", zap.String("id", announcement.ID))
|
||
return nil
|
||
}
|
||
|
||
// ArchiveAnnouncement 归档公告
|
||
func (s *AnnouncementApplicationServiceImpl) ArchiveAnnouncement(ctx context.Context, cmd *commands.ArchiveAnnouncementCommand) error {
|
||
// 1. 获取公告
|
||
announcement, err := s.announcementRepo.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.announcementService.CanArchive(&announcement); err != nil {
|
||
return fmt.Errorf("无法归档公告: %w", err)
|
||
}
|
||
|
||
// 3. 归档公告
|
||
announcement.Status = entities.AnnouncementStatusArchived
|
||
|
||
// 4. 保存更新
|
||
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||
return fmt.Errorf("归档公告失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("归档公告成功", zap.String("id", announcement.ID))
|
||
return nil
|
||
}
|
||
|
||
// SchedulePublishAnnouncement 定时发布公告
|
||
func (s *AnnouncementApplicationServiceImpl) SchedulePublishAnnouncement(ctx context.Context, cmd *commands.SchedulePublishAnnouncementCommand) 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. 获取公告
|
||
announcement, err := s.announcementRepo.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 err := s.announcementService.CanSchedulePublish(&announcement, scheduledTime); err != nil {
|
||
return fmt.Errorf("无法设置定时发布: %w", err)
|
||
}
|
||
|
||
// 4. 取消旧任务(如果存在)
|
||
if err := s.taskManager.CancelTask(ctx, cmd.ID); err != nil {
|
||
s.logger.Warn("取消旧任务失败", zap.String("announcement_id", cmd.ID), zap.Error(err))
|
||
}
|
||
|
||
// 5. 创建任务工厂
|
||
taskFactory := task_entities.NewTaskFactoryWithManager(s.taskManager)
|
||
|
||
// 6. 创建并异步入队公告发布任务
|
||
if err := taskFactory.CreateAndEnqueueAnnouncementPublishTask(
|
||
ctx,
|
||
cmd.ID,
|
||
scheduledTime,
|
||
"system", // 暂时使用系统用户ID
|
||
); err != nil {
|
||
s.logger.Error("创建并入队公告发布任务失败", zap.Error(err))
|
||
return fmt.Errorf("创建定时发布任务失败: %w", err)
|
||
}
|
||
|
||
// 7. 设置定时发布
|
||
if err := announcement.SchedulePublish(scheduledTime); err != nil {
|
||
return fmt.Errorf("设置定时发布失败: %w", err)
|
||
}
|
||
|
||
// 8. 保存更新
|
||
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||
return fmt.Errorf("设置定时发布失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("设置定时发布成功", zap.String("id", announcement.ID), zap.Time("scheduled_at", scheduledTime))
|
||
return nil
|
||
}
|
||
|
||
// UpdateSchedulePublishAnnouncement 更新定时发布公告
|
||
func (s *AnnouncementApplicationServiceImpl) UpdateSchedulePublishAnnouncement(ctx context.Context, cmd *commands.UpdateSchedulePublishAnnouncementCommand) 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. 获取公告
|
||
announcement, err := s.announcementRepo.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 !announcement.IsScheduled() {
|
||
return fmt.Errorf("公告未设置定时发布,无法修改时间")
|
||
}
|
||
|
||
// 4. 取消旧任务
|
||
if err := s.taskManager.CancelTask(ctx, cmd.ID); err != nil {
|
||
s.logger.Warn("取消旧任务失败", zap.String("announcement_id", cmd.ID), zap.Error(err))
|
||
}
|
||
|
||
// 5. 创建任务工厂
|
||
taskFactory := task_entities.NewTaskFactoryWithManager(s.taskManager)
|
||
|
||
// 6. 创建并异步入队新的公告发布任务
|
||
if err := taskFactory.CreateAndEnqueueAnnouncementPublishTask(
|
||
ctx,
|
||
cmd.ID,
|
||
scheduledTime,
|
||
"system", // 暂时使用系统用户ID
|
||
); err != nil {
|
||
s.logger.Error("创建并入队公告发布任务失败", zap.Error(err))
|
||
return fmt.Errorf("创建定时发布任务失败: %w", err)
|
||
}
|
||
|
||
// 7. 更新定时发布时间
|
||
if err := announcement.UpdateSchedulePublish(scheduledTime); err != nil {
|
||
return fmt.Errorf("更新定时发布时间失败: %w", err)
|
||
}
|
||
|
||
// 8. 保存更新
|
||
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||
return fmt.Errorf("修改定时发布时间失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("修改定时发布时间成功", zap.String("id", announcement.ID), zap.Time("scheduled_at", scheduledTime))
|
||
return nil
|
||
}
|
||
|
||
// CancelSchedulePublishAnnouncement 取消定时发布公告
|
||
func (s *AnnouncementApplicationServiceImpl) CancelSchedulePublishAnnouncement(ctx context.Context, cmd *commands.CancelSchedulePublishAnnouncementCommand) error {
|
||
// 1. 获取公告
|
||
announcement, err := s.announcementRepo.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 !announcement.IsScheduled() {
|
||
return fmt.Errorf("公告未设置定时发布,无需取消")
|
||
}
|
||
|
||
// 3. 取消任务
|
||
if err := s.taskManager.CancelTask(ctx, cmd.ID); err != nil {
|
||
s.logger.Warn("取消任务失败", zap.String("announcement_id", cmd.ID), zap.Error(err))
|
||
// 继续执行,即使取消任务失败也尝试取消定时发布状态
|
||
}
|
||
|
||
// 4. 取消定时发布
|
||
if err := announcement.CancelSchedulePublish(); err != nil {
|
||
return fmt.Errorf("取消定时发布失败: %w", err)
|
||
}
|
||
|
||
// 5. 保存更新
|
||
if err := s.announcementRepo.Update(ctx, announcement); err != nil {
|
||
s.logger.Error("更新公告失败", zap.String("id", announcement.ID), zap.Error(err))
|
||
return fmt.Errorf("取消定时发布失败: %w", err)
|
||
}
|
||
|
||
s.logger.Info("取消定时发布成功", zap.String("id", announcement.ID))
|
||
return nil
|
||
}
|
||
|
||
// GetAnnouncementStats 获取公告统计信息
|
||
func (s *AnnouncementApplicationServiceImpl) GetAnnouncementStats(ctx context.Context) (*responses.AnnouncementStatsResponse, error) {
|
||
// 1. 统计总数
|
||
total, err := s.announcementRepo.CountByStatus(ctx, entities.AnnouncementStatusDraft)
|
||
if err != nil {
|
||
s.logger.Error("统计公告总数失败", zap.Error(err))
|
||
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
||
}
|
||
|
||
// 2. 统计各状态数量
|
||
published, err := s.announcementRepo.CountByStatus(ctx, entities.AnnouncementStatusPublished)
|
||
if err != nil {
|
||
s.logger.Error("统计已发布公告数失败", zap.Error(err))
|
||
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
||
}
|
||
|
||
draft, err := s.announcementRepo.CountByStatus(ctx, entities.AnnouncementStatusDraft)
|
||
if err != nil {
|
||
s.logger.Error("统计草稿公告数失败", zap.Error(err))
|
||
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
||
}
|
||
|
||
archived, err := s.announcementRepo.CountByStatus(ctx, entities.AnnouncementStatusArchived)
|
||
if err != nil {
|
||
s.logger.Error("统计归档公告数失败", zap.Error(err))
|
||
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
||
}
|
||
|
||
// 3. 统计定时发布数量(需要查询有scheduled_at的草稿)
|
||
scheduled, err := s.announcementRepo.FindScheduled(ctx)
|
||
if err != nil {
|
||
s.logger.Error("统计定时发布公告数失败", zap.Error(err))
|
||
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
||
}
|
||
|
||
response := &responses.AnnouncementStatsResponse{
|
||
TotalAnnouncements: total + published + archived,
|
||
PublishedAnnouncements: published,
|
||
DraftAnnouncements: draft,
|
||
ArchivedAnnouncements: archived,
|
||
ScheduledAnnouncements: int64(len(scheduled)),
|
||
}
|
||
|
||
return response, nil
|
||
}
|