Security fixes: - Fix sensitive data exposure: UserResponse no longer contains email/phone - Add UserSelfResponse for authenticated user's own data - Protect Gorse endpoints with admin role requirement - Add rate limiting (10 req/min) to auth endpoints - Fix CORS configuration to use allowed origins list - Add pagination parameter validation (max 100 per page) Changes: - dto/dto.go: Add UserSelfResponse type - dto/user_converter.go: Add ConvertUserToSelfResponse - handler/user_handler.go: Use secure response types - middleware/cors.go: Implement origin whitelist - middleware/ratelimit.go: Enhance rate limiter - router/router.go: Add auth rate limit, protect Gorse - service/admin_*.go: Use ConvertUserToResponse
222 lines
6.8 KiB
Go
222 lines
6.8 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
"carrot_bbs/internal/dto"
|
|
"carrot_bbs/internal/model"
|
|
"carrot_bbs/internal/repository"
|
|
)
|
|
|
|
// AdminCommentService 管理端评论服务接口
|
|
type AdminCommentService interface {
|
|
// GetCommentList 获取评论列表(分页、搜索、状态筛选)
|
|
GetCommentList(ctx context.Context, page, pageSize int, keyword, postID, authorID, status, startDate, endDate string) ([]dto.AdminCommentListResponse, int64, error)
|
|
// GetCommentDetail 获取评论详情
|
|
GetCommentDetail(ctx context.Context, commentID string) (*dto.AdminCommentDetailResponse, error)
|
|
// DeleteComment 删除评论
|
|
DeleteComment(ctx context.Context, commentID string) error
|
|
// BatchDeleteComments 批量删除评论
|
|
BatchDeleteComments(ctx context.Context, ids []string) (*dto.AdminBatchOperationResponse, error)
|
|
// GetCommentsByPostID 获取帖子的评论列表
|
|
GetCommentsByPostID(ctx context.Context, postID string, page, pageSize int) ([]dto.AdminCommentListResponse, int64, error)
|
|
}
|
|
|
|
// adminCommentServiceImpl 管理端评论服务实现
|
|
type adminCommentServiceImpl struct {
|
|
commentRepo *repository.CommentRepository
|
|
postRepo *repository.PostRepository
|
|
}
|
|
|
|
// NewAdminCommentService 创建管理端评论服务
|
|
func NewAdminCommentService(commentRepo *repository.CommentRepository, postRepo *repository.PostRepository) AdminCommentService {
|
|
return &adminCommentServiceImpl{
|
|
commentRepo: commentRepo,
|
|
postRepo: postRepo,
|
|
}
|
|
}
|
|
|
|
// GetCommentList 获取评论列表
|
|
func (s *adminCommentServiceImpl) GetCommentList(ctx context.Context, page, pageSize int, keyword, postID, authorID, status, startDate, endDate string) ([]dto.AdminCommentListResponse, int64, error) {
|
|
filter := repository.AdminCommentListFilter{
|
|
Keyword: keyword,
|
|
PostID: postID,
|
|
AuthorID: authorID,
|
|
Status: status,
|
|
StartDate: startDate,
|
|
EndDate: endDate,
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
}
|
|
|
|
comments, total, err := s.commentRepo.GetAdminCommentList(filter)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
responses := make([]dto.AdminCommentListResponse, len(comments))
|
|
for i, comment := range comments {
|
|
responses[i] = s.convertCommentToAdminListResponse(comment)
|
|
}
|
|
|
|
return responses, total, nil
|
|
}
|
|
|
|
// GetCommentDetail 获取评论详情
|
|
func (s *adminCommentServiceImpl) GetCommentDetail(ctx context.Context, commentID string) (*dto.AdminCommentDetailResponse, error) {
|
|
comment, err := s.commentRepo.GetAdminCommentByID(commentID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return s.convertCommentToAdminDetailResponse(comment), nil
|
|
}
|
|
|
|
// DeleteComment 删除评论
|
|
func (s *adminCommentServiceImpl) DeleteComment(ctx context.Context, commentID string) error {
|
|
return s.commentRepo.Delete(commentID)
|
|
}
|
|
|
|
// BatchDeleteComments 批量删除评论
|
|
func (s *adminCommentServiceImpl) BatchDeleteComments(ctx context.Context, ids []string) (*dto.AdminBatchOperationResponse, error) {
|
|
failedIDs, err := s.commentRepo.BatchDelete(ids)
|
|
if err != nil {
|
|
return &dto.AdminBatchOperationResponse{
|
|
Success: false,
|
|
Message: "批量删除失败",
|
|
Failed: ids,
|
|
}, err
|
|
}
|
|
|
|
if len(failedIDs) == 0 {
|
|
return &dto.AdminBatchOperationResponse{
|
|
Success: true,
|
|
Message: "批量删除成功",
|
|
}, nil
|
|
}
|
|
|
|
return &dto.AdminBatchOperationResponse{
|
|
Success: true,
|
|
Message: "部分评论删除成功",
|
|
Failed: failedIDs,
|
|
}, nil
|
|
}
|
|
|
|
// GetCommentsByPostID 获取帖子的评论列表
|
|
func (s *adminCommentServiceImpl) GetCommentsByPostID(ctx context.Context, postID string, page, pageSize int) ([]dto.AdminCommentListResponse, int64, error) {
|
|
comments, total, err := s.commentRepo.GetCommentsByPostIDForAdmin(postID, page, pageSize)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
responses := make([]dto.AdminCommentListResponse, len(comments))
|
|
for i, comment := range comments {
|
|
responses[i] = s.convertCommentToAdminListResponse(comment)
|
|
}
|
|
|
|
return responses, total, nil
|
|
}
|
|
|
|
// convertCommentToAdminListResponse 转换评论为管理端列表响应
|
|
func (s *adminCommentServiceImpl) convertCommentToAdminListResponse(comment *model.Comment) dto.AdminCommentListResponse {
|
|
// 解析图片
|
|
var images []dto.CommentImageResponse
|
|
if comment.Images != "" {
|
|
var urls []string
|
|
if err := json.Unmarshal([]byte(comment.Images), &urls); err == nil {
|
|
images = make([]dto.CommentImageResponse, len(urls))
|
|
for i, url := range urls {
|
|
images[i] = dto.CommentImageResponse{URL: url}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 作者信息
|
|
var author *dto.UserResponse
|
|
if comment.User != nil {
|
|
author = dto.ConvertUserToResponse(comment.User)
|
|
}
|
|
|
|
// 帖子简要信息
|
|
var postInfo *dto.AdminCommentPostInfo
|
|
if comment.PostID != "" {
|
|
postInfo = &dto.AdminCommentPostInfo{
|
|
ID: comment.PostID,
|
|
}
|
|
// 尝试获取帖子标题
|
|
post, err := s.postRepo.GetByID(comment.PostID)
|
|
if err == nil && post != nil {
|
|
postInfo.Title = post.Title
|
|
}
|
|
}
|
|
|
|
return dto.AdminCommentListResponse{
|
|
ID: comment.ID,
|
|
PostID: comment.PostID,
|
|
UserID: comment.UserID,
|
|
ParentID: comment.ParentID,
|
|
RootID: comment.RootID,
|
|
Content: comment.Content,
|
|
Images: images,
|
|
Status: string(comment.Status),
|
|
LikesCount: comment.LikesCount,
|
|
RepliesCount: comment.RepliesCount,
|
|
CreatedAt: comment.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
|
UpdatedAt: comment.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
|
Author: author,
|
|
Post: postInfo,
|
|
}
|
|
}
|
|
|
|
// convertCommentToAdminDetailResponse 转换评论为管理端详情响应
|
|
func (s *adminCommentServiceImpl) convertCommentToAdminDetailResponse(comment *model.Comment) *dto.AdminCommentDetailResponse {
|
|
// 解析图片
|
|
var images []dto.CommentImageResponse
|
|
if comment.Images != "" {
|
|
var urls []string
|
|
if err := json.Unmarshal([]byte(comment.Images), &urls); err == nil {
|
|
images = make([]dto.CommentImageResponse, len(urls))
|
|
for i, url := range urls {
|
|
images[i] = dto.CommentImageResponse{URL: url}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 作者信息
|
|
var author *dto.UserResponse
|
|
if comment.User != nil {
|
|
author = dto.ConvertUserToResponse(comment.User)
|
|
}
|
|
|
|
// 帖子简要信息
|
|
var postInfo *dto.AdminCommentPostInfo
|
|
if comment.PostID != "" {
|
|
postInfo = &dto.AdminCommentPostInfo{
|
|
ID: comment.PostID,
|
|
}
|
|
// 尝试获取帖子标题
|
|
post, err := s.postRepo.GetByID(comment.PostID)
|
|
if err == nil && post != nil {
|
|
postInfo.Title = post.Title
|
|
}
|
|
}
|
|
|
|
return &dto.AdminCommentDetailResponse{
|
|
ID: comment.ID,
|
|
PostID: comment.PostID,
|
|
UserID: comment.UserID,
|
|
ParentID: comment.ParentID,
|
|
RootID: comment.RootID,
|
|
Content: comment.Content,
|
|
Images: images,
|
|
Status: string(comment.Status),
|
|
LikesCount: comment.LikesCount,
|
|
RepliesCount: comment.RepliesCount,
|
|
CreatedAt: comment.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
|
UpdatedAt: comment.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
|
Author: author,
|
|
Post: postInfo,
|
|
}
|
|
}
|