- Removed Gorse-related configurations, handlers, and dependencies from the codebase. - Introduced HotRank feature with configuration options for ranking posts based on recent activity. - Updated application structure to support HotRank processing, including new caching mechanisms and database interactions. - Cleaned up related DTOs and repository methods to reflect the removal of Gorse and the addition of HotRank functionality.
348 lines
10 KiB
Go
348 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,
|
|
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,
|
|
}
|
|
}
|