Files
backend/internal/service/admin_post_service.go
lafay 081e44c4ff
Some checks failed
Build Backend / build (push) Failing after 13m9s
Build Backend / build-docker (push) Has been skipped
chore(ci, dto): update build workflow and enhance post DTOs
- Added NODE_NO_WARNINGS environment variable to build and deploy workflows.
- Upgraded actions/upload-artifact and actions/download-artifact to version 4 in the build workflow.
- Introduced ContentEditedAt field in PostResponse and PostDetailResponse DTOs to track content modification timestamps.
- Updated post conversion functions to include ContentEditedAt in responses.
- Ensured ContentEditedAt is set during post updates in the repository.
2026-03-23 14:08:36 +08:00

349 lines
10 KiB
Go

package service
import (
"context"
"carrot_bbs/internal/cache"
"carrot_bbs/internal/dto"
"carrot_bbs/internal/model"
"carrot_bbs/internal/repository"
)
// AdminPostService 管理端帖子服务接口
type AdminPostService interface {
// GetPostList 获取帖子列表(分页、搜索、状态筛选)
GetPostList(ctx context.Context, page, pageSize int, keyword, status, authorID, startDate, endDate string) ([]dto.AdminPostListResponse, int64, error)
// GetPostDetail 获取帖子详情
GetPostDetail(ctx context.Context, postID string) (*dto.AdminPostDetailResponse, error)
// DeletePost 删除帖子
DeletePost(ctx context.Context, postID string) error
// ModeratePost 审核帖子
ModeratePost(ctx context.Context, postID string, status model.PostStatus, reason, reviewedBy string) (*dto.AdminPostDetailResponse, error)
// BatchDeletePosts 批量删除帖子
BatchDeletePosts(ctx context.Context, ids []string) (*dto.AdminBatchOperationResponse, error)
// BatchModeratePosts 批量审核帖子
BatchModeratePosts(ctx context.Context, ids []string, status model.PostStatus, reviewedBy string) (*dto.AdminBatchOperationResponse, error)
// SetPostPin 置顶/取消置顶帖子
SetPostPin(ctx context.Context, postID string, isPinned bool) (*dto.AdminPostDetailResponse, error)
// SetPostFeature 加精/取消加精帖子
SetPostFeature(ctx context.Context, postID string, isFeatured bool) (*dto.AdminPostDetailResponse, error)
// 日志服务设置
SetLogService(logService *LogService)
}
// adminPostServiceImpl 管理端帖子服务实现
type adminPostServiceImpl struct {
postRepo *repository.PostRepository
cache cache.Cache
logService *LogService
}
// NewAdminPostService 创建管理端帖子服务
func NewAdminPostService(postRepo *repository.PostRepository, cacheBackend cache.Cache) AdminPostService {
return &adminPostServiceImpl{
postRepo: postRepo,
cache: cacheBackend,
logService: nil,
}
}
// SetLogService 设置日志服务
func (s *adminPostServiceImpl) SetLogService(logService *LogService) {
s.logService = logService
}
// GetPostList 获取帖子列表
func (s *adminPostServiceImpl) GetPostList(ctx context.Context, page, pageSize int, keyword, status, authorID, startDate, endDate string) ([]dto.AdminPostListResponse, int64, error) {
query := repository.AdminPostListQuery{
Page: page,
PageSize: pageSize,
Keyword: keyword,
Status: status,
AuthorID: authorID,
StartDate: startDate,
EndDate: endDate,
}
posts, total, err := s.postRepo.AdminList(query)
if err != nil {
return nil, 0, err
}
responses := make([]dto.AdminPostListResponse, len(posts))
for i, post := range posts {
responses[i] = convertPostToAdminListResponse(post)
}
return responses, total, nil
}
// GetPostDetail 获取帖子详情
func (s *adminPostServiceImpl) GetPostDetail(ctx context.Context, postID string) (*dto.AdminPostDetailResponse, error) {
post, err := s.postRepo.GetByIDForAdmin(postID)
if err != nil {
return nil, err
}
return convertPostToAdminDetailResponse(post), nil
}
// DeletePost 删除帖子
func (s *adminPostServiceImpl) DeletePost(ctx context.Context, postID string) error {
post, err := s.postRepo.GetByID(postID)
if err != nil {
return err
}
err = s.postRepo.Delete(postID)
if err != nil {
return err
}
// 失效缓存
cache.InvalidatePostDetail(s.cache, postID)
cache.InvalidatePostList(s.cache)
// 记录操作日志
if s.logService != nil {
s.logService.OperationLog.RecordOperation(&model.OperationLog{
UserID: post.UserID,
Operation: string(model.OpAdminDeletePost),
Module: "admin",
TargetType: "post",
TargetID: postID,
Status: "success",
})
}
return nil
}
// ModeratePost 审核帖子
func (s *adminPostServiceImpl) ModeratePost(ctx context.Context, postID string, status model.PostStatus, reason, reviewedBy string) (*dto.AdminPostDetailResponse, error) {
// 先检查帖子是否存在
_, err := s.postRepo.GetByIDForAdmin(postID)
if err != nil {
return nil, err
}
// 更新审核状态
if err := s.postRepo.UpdateModerationStatus(postID, status, reason, reviewedBy); err != nil {
return nil, err
}
// 失效缓存
cache.InvalidatePostDetail(s.cache, postID)
cache.InvalidatePostList(s.cache)
// 重新获取帖子信息
post, err := s.postRepo.GetByIDForAdmin(postID)
if err != nil {
return nil, err
}
return convertPostToAdminDetailResponse(post), nil
}
// BatchDeletePosts 批量删除帖子
func (s *adminPostServiceImpl) BatchDeletePosts(ctx context.Context, ids []string) (*dto.AdminBatchOperationResponse, error) {
failedIDs, err := s.postRepo.BatchDelete(ids)
if err != nil {
return &dto.AdminBatchOperationResponse{
Success: false,
Message: "批量删除失败",
Failed: ids,
}, err
}
// 失效缓存
for _, id := range ids {
cache.InvalidatePostDetail(s.cache, id)
}
cache.InvalidatePostList(s.cache)
if len(failedIDs) == 0 {
return &dto.AdminBatchOperationResponse{
Success: true,
Message: "批量删除成功",
}, nil
}
return &dto.AdminBatchOperationResponse{
Success: true,
Message: "部分帖子删除成功",
Failed: failedIDs,
}, nil
}
// BatchModeratePosts 批量审核帖子
func (s *adminPostServiceImpl) BatchModeratePosts(ctx context.Context, ids []string, status model.PostStatus, reviewedBy string) (*dto.AdminBatchOperationResponse, error) {
failedIDs, err := s.postRepo.BatchUpdateStatus(ids, status, reviewedBy)
if err != nil {
return &dto.AdminBatchOperationResponse{
Success: false,
Message: "批量审核失败",
Failed: ids,
}, err
}
// 失效缓存
for _, id := range ids {
cache.InvalidatePostDetail(s.cache, id)
}
cache.InvalidatePostList(s.cache)
if len(failedIDs) == 0 {
return &dto.AdminBatchOperationResponse{
Success: true,
Message: "批量审核成功",
}, nil
}
return &dto.AdminBatchOperationResponse{
Success: true,
Message: "部分帖子审核成功",
Failed: failedIDs,
}, nil
}
// SetPostPin 置顶/取消置顶帖子
func (s *adminPostServiceImpl) SetPostPin(ctx context.Context, postID string, isPinned bool) (*dto.AdminPostDetailResponse, error) {
// 先检查帖子是否存在
_, err := s.postRepo.GetByIDForAdmin(postID)
if err != nil {
return nil, err
}
// 更新置顶状态
if err := s.postRepo.UpdatePinStatus(postID, isPinned); err != nil {
return nil, err
}
// 失效缓存
cache.InvalidatePostDetail(s.cache, postID)
cache.InvalidatePostList(s.cache)
// 重新获取帖子信息
post, err := s.postRepo.GetByIDForAdmin(postID)
if err != nil {
return nil, err
}
return convertPostToAdminDetailResponse(post), nil
}
// SetPostFeature 加精/取消加精帖子
func (s *adminPostServiceImpl) SetPostFeature(ctx context.Context, postID string, isFeatured bool) (*dto.AdminPostDetailResponse, error) {
// 先检查帖子是否存在
_, err := s.postRepo.GetByIDForAdmin(postID)
if err != nil {
return nil, err
}
// 更新加精状态
if err := s.postRepo.UpdateFeatureStatus(postID, isFeatured); err != nil {
return nil, err
}
// 失效缓存
cache.InvalidatePostDetail(s.cache, postID)
cache.InvalidatePostList(s.cache)
// 重新获取帖子信息
post, err := s.postRepo.GetByIDForAdmin(postID)
if err != nil {
return nil, err
}
return convertPostToAdminDetailResponse(post), nil
}
// convertPostToAdminListResponse 转换帖子为管理端列表响应
func convertPostToAdminListResponse(post *model.Post) dto.AdminPostListResponse {
images := make([]dto.PostImageResponse, len(post.Images))
for i, img := range post.Images {
images[i] = dto.PostImageResponse{
ID: img.ID,
URL: img.URL,
ThumbnailURL: img.ThumbnailURL,
Width: img.Width,
Height: img.Height,
}
}
var author *dto.UserResponse
if post.User != nil {
author = dto.ConvertUserToResponse(post.User)
}
return dto.AdminPostListResponse{
ID: post.ID,
UserID: post.UserID,
Title: post.Title,
Content: post.Content,
Images: images,
Status: string(post.Status),
LikesCount: post.LikesCount,
CommentsCount: post.CommentsCount,
FavoritesCount: post.FavoritesCount,
ViewsCount: post.ViewsCount,
IsPinned: post.IsPinned,
IsFeatured: post.IsFeatured,
IsLocked: post.IsLocked,
RejectReason: post.RejectReason,
ReviewedAt: dto.FormatTimePointer(post.ReviewedAt),
ReviewedBy: post.ReviewedBy,
CreatedAt: post.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: post.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
ContentEditedAt: dto.FormatTimePointer(post.ContentEditedAt),
Author: author,
}
}
// convertPostToAdminDetailResponse 转换帖子为管理端详情响应
func convertPostToAdminDetailResponse(post *model.Post) *dto.AdminPostDetailResponse {
images := make([]dto.PostImageResponse, len(post.Images))
for i, img := range post.Images {
images[i] = dto.PostImageResponse{
ID: img.ID,
URL: img.URL,
ThumbnailURL: img.ThumbnailURL,
Width: img.Width,
Height: img.Height,
}
}
var author *dto.UserResponse
if post.User != nil {
author = dto.ConvertUserToResponse(post.User)
}
return &dto.AdminPostDetailResponse{
ID: post.ID,
UserID: post.UserID,
CommunityID: post.CommunityID,
Title: post.Title,
Content: post.Content,
Images: images,
Status: string(post.Status),
LikesCount: post.LikesCount,
CommentsCount: post.CommentsCount,
FavoritesCount: post.FavoritesCount,
SharesCount: post.SharesCount,
ViewsCount: post.ViewsCount,
HotScore: post.HotScore,
IsPinned: post.IsPinned,
IsFeatured: post.IsFeatured,
IsLocked: post.IsLocked,
IsVote: post.IsVote,
RejectReason: post.RejectReason,
ReviewedAt: dto.FormatTimePointer(post.ReviewedAt),
ReviewedBy: post.ReviewedBy,
CreatedAt: post.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: post.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
ContentEditedAt: dto.FormatTimePointer(post.ContentEditedAt),
Author: author,
}
}