- Remove BOM from all Go source files - Standardize import ordering and whitespace across handlers and services - Replace PostgreSQL full-text search with ILIKE pattern matching in post and user repositories - Enhance JPush client with TLS transport, rate limit monitoring, and simplified API - Refactor PushService to include userID in device management methods - Add MaxDevicesPerUser limit and extract helper functions for push payload construction
344 lines
10 KiB
Go
344 lines
10 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
|
|
"with_you/internal/cache"
|
|
"with_you/internal/dto"
|
|
"with_you/internal/model"
|
|
"with_you/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)
|
|
}
|
|
|
|
// adminPostServiceImpl 管理端帖子服务实现
|
|
type adminPostServiceImpl struct {
|
|
postRepo repository.PostRepository
|
|
cache cache.Cache
|
|
logService *LogService
|
|
}
|
|
|
|
// NewAdminPostService 创建管理端帖子服务
|
|
func NewAdminPostService(postRepo repository.PostRepository, cacheBackend cache.Cache, logService *LogService) AdminPostService {
|
|
return &adminPostServiceImpl{
|
|
postRepo: postRepo,
|
|
cache: cacheBackend,
|
|
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,
|
|
PreviewURL: img.PreviewURL,
|
|
PreviewURLLarge: img.PreviewURLLarge,
|
|
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,
|
|
PreviewURL: img.PreviewURL,
|
|
PreviewURLLarge: img.PreviewURLLarge,
|
|
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,
|
|
ChannelID: post.ChannelID,
|
|
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,
|
|
}
|
|
}
|