feat(admin): add comprehensive admin management system
- Add admin handlers and services for users, posts, comments, groups, and dashboard - Implement role permission management with ability to view and update permissions - Add database seeding for roles and Casbin permission policies - Upgrade Casbin from v2 to v3 with GORM adapter for persistent policy storage - Add admin-specific repository methods with filtering and pagination - Register new admin API routes under /api/v1/admin/*
This commit is contained in:
243
internal/service/admin_comment_service.go
Normal file
243
internal/service/admin_comment_service.go
Normal file
@@ -0,0 +1,243 @@
|
||||
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.UserResponse{
|
||||
ID: comment.User.ID,
|
||||
Username: comment.User.Username,
|
||||
Nickname: comment.User.Nickname,
|
||||
Email: comment.User.Email,
|
||||
Phone: comment.User.Phone,
|
||||
Avatar: comment.User.Avatar,
|
||||
Bio: comment.User.Bio,
|
||||
Website: comment.User.Website,
|
||||
Location: comment.User.Location,
|
||||
CreatedAt: comment.User.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
}
|
||||
|
||||
// 帖子简要信息
|
||||
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.UserResponse{
|
||||
ID: comment.User.ID,
|
||||
Username: comment.User.Username,
|
||||
Nickname: comment.User.Nickname,
|
||||
Email: comment.User.Email,
|
||||
Phone: comment.User.Phone,
|
||||
Avatar: comment.User.Avatar,
|
||||
Bio: comment.User.Bio,
|
||||
Website: comment.User.Website,
|
||||
Location: comment.User.Location,
|
||||
CreatedAt: comment.User.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
}
|
||||
|
||||
// 帖子简要信息
|
||||
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,
|
||||
}
|
||||
}
|
||||
444
internal/service/admin_dashboard_service.go
Normal file
444
internal/service/admin_dashboard_service.go
Normal file
@@ -0,0 +1,444 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/repository"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AdminDashboardService 管理端仪表盘服务接口
|
||||
type AdminDashboardService interface {
|
||||
// GetStats 获取统计数据
|
||||
GetStats(ctx context.Context) (*dto.DashboardStatsResponse, error)
|
||||
// GetUserActivityTrend 获取用户活跃度趋势
|
||||
GetUserActivityTrend(ctx context.Context, days int) ([]dto.UserActivityTrendResponse, error)
|
||||
// GetContentStats 获取内容统计
|
||||
GetContentStats(ctx context.Context) (*dto.ContentStatsResponse, error)
|
||||
// GetPendingContent 获取待审核内容
|
||||
GetPendingContent(ctx context.Context, limit int) ([]dto.PendingContentResponse, error)
|
||||
// GetOnlineUsers 获取在线用户数
|
||||
GetOnlineUsers(ctx context.Context) (*dto.OnlineUsersResponse, error)
|
||||
}
|
||||
|
||||
// adminDashboardServiceImpl 管理端仪表盘服务实现
|
||||
type adminDashboardServiceImpl struct {
|
||||
db *gorm.DB
|
||||
userRepo *repository.UserRepository
|
||||
postRepo *repository.PostRepository
|
||||
commentRepo *repository.CommentRepository
|
||||
groupRepo repository.GroupRepository
|
||||
activityRepo *repository.UserActivityRepository
|
||||
}
|
||||
|
||||
// NewAdminDashboardService 创建管理端仪表盘服务
|
||||
func NewAdminDashboardService(
|
||||
db *gorm.DB,
|
||||
userRepo *repository.UserRepository,
|
||||
postRepo *repository.PostRepository,
|
||||
commentRepo *repository.CommentRepository,
|
||||
groupRepo repository.GroupRepository,
|
||||
activityRepo *repository.UserActivityRepository,
|
||||
) AdminDashboardService {
|
||||
return &adminDashboardServiceImpl{
|
||||
db: db,
|
||||
userRepo: userRepo,
|
||||
postRepo: postRepo,
|
||||
commentRepo: commentRepo,
|
||||
groupRepo: groupRepo,
|
||||
activityRepo: activityRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// GetStats 获取统计数据
|
||||
func (s *adminDashboardServiceImpl) GetStats(ctx context.Context) (*dto.DashboardStatsResponse, error) {
|
||||
var stats dto.DashboardStatsResponse
|
||||
|
||||
// 获取当前时间
|
||||
now := time.Now()
|
||||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
weekStart := todayStart.AddDate(0, 0, -int(now.Weekday()))
|
||||
|
||||
// 总用户数
|
||||
if err := s.db.Model(&model.User{}).Count(&stats.TotalUsers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 今日新增用户
|
||||
if err := s.db.Model(&model.User{}).Where("created_at >= ?", todayStart).Count(&stats.TodayNewUsers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 今日活跃用户
|
||||
todayActive, err := s.activityRepo.GetDAU(ctx, now)
|
||||
if err != nil {
|
||||
// 如果出错,尝试从数据库获取
|
||||
stats.ActiveUsersToday = 0
|
||||
} else {
|
||||
stats.ActiveUsersToday = todayActive
|
||||
}
|
||||
|
||||
// 本周活跃用户(使用 WAU)
|
||||
year, week := now.ISOWeek()
|
||||
weekActive, err := s.activityRepo.GetWAUFromRedis(ctx, year, week)
|
||||
if err != nil {
|
||||
// 如果 Redis 获取失败,从数据库计算
|
||||
var weekActiveCount int64
|
||||
err := s.db.Model(&model.UserActiveLog{}).
|
||||
Where("active_date >= ?", weekStart).
|
||||
Distinct("user_id").
|
||||
Count(&weekActiveCount).Error
|
||||
if err == nil {
|
||||
stats.ActiveUsersWeek = weekActiveCount
|
||||
}
|
||||
} else {
|
||||
stats.ActiveUsersWeek = weekActive
|
||||
}
|
||||
|
||||
// 本月活跃用户(使用 MAU)
|
||||
monthActive, err := s.activityRepo.GetMAUFromRedis(ctx, now.Year(), int(now.Month()))
|
||||
if err != nil {
|
||||
// 如果 Redis 获取失败,从数据库计算
|
||||
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||
var monthActiveCount int64
|
||||
err := s.db.Model(&model.UserActiveLog{}).
|
||||
Where("active_date >= ?", monthStart).
|
||||
Distinct("user_id").
|
||||
Count(&monthActiveCount).Error
|
||||
if err == nil {
|
||||
stats.ActiveUsersMonth = monthActiveCount
|
||||
}
|
||||
} else {
|
||||
stats.ActiveUsersMonth = monthActive
|
||||
}
|
||||
|
||||
// 获取留存率数据(从统计快照表获取)
|
||||
retentionStats := s.getRetentionStats(ctx, now)
|
||||
stats.Retention1Day = retentionStats.Retention1Day
|
||||
stats.Retention7Day = retentionStats.Retention7Day
|
||||
stats.Retention30Day = retentionStats.Retention30Day
|
||||
|
||||
// 总帖子数
|
||||
if err := s.db.Model(&model.Post{}).Count(&stats.TotalPosts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 今日帖子数
|
||||
if err := s.db.Model(&model.Post{}).Where("created_at >= ?", todayStart).Count(&stats.TodayPosts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 待审核帖子数
|
||||
if err := s.db.Model(&model.Post{}).Where("status = ?", model.PostStatusPending).Count(&stats.PendingPosts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 总评论数
|
||||
if err := s.db.Model(&model.Comment{}).Count(&stats.TotalComments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 今日评论数
|
||||
if err := s.db.Model(&model.Comment{}).Where("created_at >= ?", todayStart).Count(&stats.TodayComments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 总群组数
|
||||
if err := s.db.Model(&model.Group{}).Count(&stats.TotalGroups).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 今日新建群组
|
||||
if err := s.db.Model(&model.Group{}).Where("created_at >= ?", todayStart).Count(&stats.TodayNewGroups).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 待审核评论数
|
||||
var pendingComments int64
|
||||
if err := s.db.Model(&model.Comment{}).Where("status = ?", model.CommentStatusPending).Count(&pendingComments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.PendingComments = pendingComments
|
||||
|
||||
// 计算待审核总数
|
||||
stats.PendingReview = stats.PendingPosts + stats.PendingComments
|
||||
stats.PendingPostsCount = stats.PendingPosts
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// GetUserActivityTrend 获取用户活跃度趋势
|
||||
func (s *adminDashboardServiceImpl) GetUserActivityTrend(ctx context.Context, days int) ([]dto.UserActivityTrendResponse, error) {
|
||||
if days <= 0 {
|
||||
days = 7
|
||||
}
|
||||
if days > 30 {
|
||||
days = 30
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
results := make([]dto.UserActivityTrendResponse, days)
|
||||
|
||||
for i := days - 1; i >= 0; i-- {
|
||||
date := now.AddDate(0, 0, -i)
|
||||
dateStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||
dateEnd := dateStart.Add(24 * time.Hour)
|
||||
|
||||
trend := dto.UserActivityTrendResponse{
|
||||
Date: dateStart.Format("2006-01-02"),
|
||||
}
|
||||
|
||||
// 新增用户数
|
||||
var newUsers int64
|
||||
if err := s.db.Model(&model.User{}).
|
||||
Where("created_at >= ? AND created_at < ?", dateStart, dateEnd).
|
||||
Count(&newUsers).Error; err == nil {
|
||||
trend.NewUsers = newUsers
|
||||
}
|
||||
|
||||
// 活跃用户数
|
||||
activeUsers, err := s.activityRepo.GetDAU(ctx, date)
|
||||
if err == nil {
|
||||
trend.ActiveUsers = activeUsers
|
||||
}
|
||||
|
||||
// 帖子数
|
||||
var posts int64
|
||||
if err := s.db.Model(&model.Post{}).
|
||||
Where("created_at >= ? AND created_at < ?", dateStart, dateEnd).
|
||||
Count(&posts).Error; err == nil {
|
||||
trend.Posts = posts
|
||||
}
|
||||
|
||||
// 评论数
|
||||
var comments int64
|
||||
if err := s.db.Model(&model.Comment{}).
|
||||
Where("created_at >= ? AND created_at < ?", dateStart, dateEnd).
|
||||
Count(&comments).Error; err == nil {
|
||||
trend.Comments = comments
|
||||
}
|
||||
|
||||
results[days-1-i] = trend
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GetContentStats 获取内容统计
|
||||
func (s *adminDashboardServiceImpl) GetContentStats(ctx context.Context) (*dto.ContentStatsResponse, error) {
|
||||
var response dto.ContentStatsResponse
|
||||
|
||||
// 内容分布
|
||||
var totalPosts, totalComments, totalGroups int64
|
||||
s.db.Model(&model.Post{}).Count(&totalPosts)
|
||||
s.db.Model(&model.Comment{}).Count(&totalComments)
|
||||
s.db.Model(&model.Group{}).Count(&totalGroups)
|
||||
|
||||
response.ContentDistribution = []dto.ContentDistributionItem{
|
||||
{Type: "posts", Name: "帖子", Count: totalPosts},
|
||||
{Type: "comments", Name: "评论", Count: totalComments},
|
||||
{Type: "groups", Name: "群组", Count: totalGroups},
|
||||
}
|
||||
|
||||
// 帖子状态分布
|
||||
var publishedCount, pendingCount, rejectedCount, draftCount int64
|
||||
s.db.Model(&model.Post{}).Where("status = ?", model.PostStatusPublished).Count(&publishedCount)
|
||||
s.db.Model(&model.Post{}).Where("status = ?", model.PostStatusPending).Count(&pendingCount)
|
||||
s.db.Model(&model.Post{}).Where("status = ?", model.PostStatusRejected).Count(&rejectedCount)
|
||||
s.db.Model(&model.Post{}).Where("status = ?", model.PostStatusDraft).Count(&draftCount)
|
||||
|
||||
response.PostStatusDistribution = []dto.PostStatusDistributionItem{
|
||||
{Status: string(model.PostStatusPublished), Name: "已发布", Count: publishedCount},
|
||||
{Status: string(model.PostStatusPending), Name: "待审核", Count: pendingCount},
|
||||
{Status: string(model.PostStatusRejected), Name: "已拒绝", Count: rejectedCount},
|
||||
{Status: string(model.PostStatusDraft), Name: "草稿", Count: draftCount},
|
||||
}
|
||||
|
||||
// 用户增长趋势(最近7天)
|
||||
now := time.Now()
|
||||
response.UserGrowth = make([]dto.UserGrowthItem, 7)
|
||||
for i := 6; i >= 0; i-- {
|
||||
date := now.AddDate(0, 0, -i)
|
||||
dateStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||
dateEnd := dateStart.Add(24 * time.Hour)
|
||||
|
||||
var newUsers int64
|
||||
s.db.Model(&model.User{}).
|
||||
Where("created_at >= ? AND created_at < ?", dateStart, dateEnd).
|
||||
Count(&newUsers)
|
||||
|
||||
response.UserGrowth[6-i] = dto.UserGrowthItem{
|
||||
Date: dateStart.Format("2006-01-02"),
|
||||
NewUsers: newUsers,
|
||||
}
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// GetPendingContent 获取待审核内容
|
||||
func (s *adminDashboardServiceImpl) GetPendingContent(ctx context.Context, limit int) ([]dto.PendingContentResponse, error) {
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
if limit > 50 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
results := make([]dto.PendingContentResponse, 0)
|
||||
|
||||
// 获取待审核帖子
|
||||
var posts []*model.Post
|
||||
if err := s.db.Preload("User").
|
||||
Where("status = ?", model.PostStatusPending).
|
||||
Order("created_at DESC").
|
||||
Limit(limit).
|
||||
Find(&posts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, post := range posts {
|
||||
item := dto.PendingContentResponse{
|
||||
ID: post.ID,
|
||||
Type: "post",
|
||||
Title: post.Title,
|
||||
Content: post.Content,
|
||||
CreatedAt: post.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
if post.User != nil {
|
||||
item.Author = &dto.PendingContentAuthor{
|
||||
ID: post.User.ID,
|
||||
Username: post.User.Username,
|
||||
Nickname: post.User.Nickname,
|
||||
Avatar: post.User.Avatar,
|
||||
}
|
||||
}
|
||||
results = append(results, item)
|
||||
}
|
||||
|
||||
// 获取待审核评论
|
||||
var comments []*model.Comment
|
||||
if err := s.db.Preload("User").
|
||||
Where("status = ?", model.CommentStatusPending).
|
||||
Order("created_at DESC").
|
||||
Limit(limit).
|
||||
Find(&comments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, comment := range comments {
|
||||
item := dto.PendingContentResponse{
|
||||
ID: comment.ID,
|
||||
Type: "comment",
|
||||
Title: "",
|
||||
Content: comment.Content,
|
||||
CreatedAt: comment.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
if comment.User != nil {
|
||||
item.Author = &dto.PendingContentAuthor{
|
||||
ID: comment.User.ID,
|
||||
Username: comment.User.Username,
|
||||
Nickname: comment.User.Nickname,
|
||||
Avatar: comment.User.Avatar,
|
||||
}
|
||||
}
|
||||
results = append(results, item)
|
||||
}
|
||||
|
||||
// 按时间排序并限制数量
|
||||
if len(results) > limit {
|
||||
results = results[:limit]
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// GetOnlineUsers 获取在线用户数
|
||||
func (s *adminDashboardServiceImpl) GetOnlineUsers(ctx context.Context) (*dto.OnlineUsersResponse, error) {
|
||||
// 获取当前日期的活跃用户数作为在线用户数的近似值
|
||||
// 实际应用中可能需要使用 WebSocket 连接数或 Redis 中的在线状态
|
||||
now := time.Now()
|
||||
count, err := s.activityRepo.GetDAU(ctx, now)
|
||||
if err != nil {
|
||||
// 如果 Redis 获取失败,从数据库获取
|
||||
var dbCount int64
|
||||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
if err := s.db.Model(&model.UserActiveLog{}).
|
||||
Where("active_date >= ?", todayStart).
|
||||
Distinct("user_id").
|
||||
Count(&dbCount).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
count = dbCount
|
||||
}
|
||||
|
||||
return &dto.OnlineUsersResponse{
|
||||
Count: count,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// retentionStatsResult 留存率结果
|
||||
type retentionStatsResult struct {
|
||||
Retention1Day int64
|
||||
Retention7Day int64
|
||||
Retention30Day int64
|
||||
}
|
||||
|
||||
// getRetentionStats 获取留存率统计数据
|
||||
func (s *adminDashboardServiceImpl) getRetentionStats(ctx context.Context, now time.Time) retentionStatsResult {
|
||||
var result retentionStatsResult
|
||||
|
||||
// 尝试从统计快照表获取昨日的留存率数据
|
||||
yesterday := now.AddDate(0, 0, -1)
|
||||
if stat, err := s.activityRepo.GetStat(ctx, "dau", yesterday); err == nil {
|
||||
result.Retention1Day = int64(stat.Retention1)
|
||||
result.Retention7Day = int64(stat.Retention7)
|
||||
result.Retention30Day = int64(stat.Retention30)
|
||||
return result
|
||||
}
|
||||
|
||||
// 如果快照表没有数据,实时计算留存率
|
||||
// 次日留存率:昨日新增用户中今日仍活跃的比例
|
||||
result.Retention1Day = s.calculateRetention(ctx, 1)
|
||||
result.Retention7Day = s.calculateRetention(ctx, 7)
|
||||
result.Retention30Day = s.calculateRetention(ctx, 30)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// calculateRetention 计算指定天数的留存率
|
||||
func (s *adminDashboardServiceImpl) calculateRetention(ctx context.Context, days int) int64 {
|
||||
now := time.Now()
|
||||
|
||||
// 获取days天前的新增用户
|
||||
targetDate := now.AddDate(0, 0, -days)
|
||||
targetDateStart := time.Date(targetDate.Year(), targetDate.Month(), targetDate.Day(), 0, 0, 0, 0, targetDate.Location())
|
||||
targetDateEnd := targetDateStart.Add(24 * time.Hour)
|
||||
|
||||
var newUsers []string
|
||||
if err := s.db.Model(&model.User{}).
|
||||
Select("id").
|
||||
Where("created_at >= ? AND created_at < ?", targetDateStart, targetDateEnd).
|
||||
Pluck("id", &newUsers).Error; err != nil || len(newUsers) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 检查这些用户在今日是否活跃
|
||||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
var activeCount int64
|
||||
if err := s.db.Model(&model.UserActiveLog{}).
|
||||
Where("user_id IN ?", newUsers).
|
||||
Where("active_date >= ?", todayStart).
|
||||
Distinct("user_id").
|
||||
Count(&activeCount).Error; err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 计算留存率(百分比)
|
||||
retention := float64(activeCount) / float64(len(newUsers)) * 100
|
||||
return int64(retention)
|
||||
}
|
||||
390
internal/service/admin_group_service.go
Normal file
390
internal/service/admin_group_service.go
Normal file
@@ -0,0 +1,390 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/repository"
|
||||
)
|
||||
|
||||
// AdminGroupService 管理端群组服务接口
|
||||
type AdminGroupService interface {
|
||||
// GetGroupList 获取群组列表(分页、搜索、筛选)
|
||||
GetGroupList(ctx context.Context, page, pageSize int, keyword, status, ownerID, startDate, endDate string) ([]dto.AdminGroupListResponse, int64, error)
|
||||
// GetGroupDetail 获取群组详情
|
||||
GetGroupDetail(ctx context.Context, groupID string) (*dto.AdminGroupDetailResponse, error)
|
||||
// GetGroupMembers 获取群组成员列表
|
||||
GetGroupMembers(ctx context.Context, groupID string, page, pageSize int) (*dto.AdminGroupMemberListResponse, error)
|
||||
// DeleteGroup 解散群组
|
||||
DeleteGroup(ctx context.Context, groupID string) error
|
||||
// UpdateGroupStatus 更新群组状态
|
||||
UpdateGroupStatus(ctx context.Context, groupID string, status string) (*dto.AdminGroupDetailResponse, error)
|
||||
// RemoveMember 移除群组成员
|
||||
RemoveMember(ctx context.Context, groupID string, userID string) error
|
||||
// TransferOwner 转让群主
|
||||
TransferOwner(ctx context.Context, groupID string, newOwnerID string) (*dto.AdminGroupDetailResponse, error)
|
||||
}
|
||||
|
||||
// adminGroupServiceImpl 管理端群组服务实现
|
||||
type adminGroupServiceImpl struct {
|
||||
groupRepo repository.GroupRepository
|
||||
userRepo *repository.UserRepository
|
||||
}
|
||||
|
||||
// NewAdminGroupService 创建管理端群组服务
|
||||
func NewAdminGroupService(
|
||||
groupRepo repository.GroupRepository,
|
||||
userRepo *repository.UserRepository,
|
||||
) AdminGroupService {
|
||||
return &adminGroupServiceImpl{
|
||||
groupRepo: groupRepo,
|
||||
userRepo: userRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// GetGroupList 获取群组列表
|
||||
func (s *adminGroupServiceImpl) GetGroupList(ctx context.Context, page, pageSize int, keyword, status, ownerID, startDate, endDate string) ([]dto.AdminGroupListResponse, int64, error) {
|
||||
groups, total, err := s.groupRepo.GetGroupList(page, pageSize, keyword, status, ownerID, startDate, endDate)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
responses := make([]dto.AdminGroupListResponse, 0, len(groups))
|
||||
for _, group := range groups {
|
||||
response := dto.AdminGroupListResponse{
|
||||
ID: group.ID,
|
||||
Name: group.Name,
|
||||
Avatar: group.Avatar,
|
||||
Description: group.Description,
|
||||
OwnerID: group.OwnerID,
|
||||
MemberCount: group.MemberCount,
|
||||
MaxMembers: group.MaxMembers,
|
||||
JoinType: int(group.JoinType),
|
||||
MuteAll: group.MuteAll,
|
||||
Status: "active", // 默认状态为active,后续可以根据需要添加状态字段
|
||||
CreatedAt: dto.FormatTime(group.CreatedAt),
|
||||
UpdatedAt: dto.FormatTime(group.UpdatedAt),
|
||||
}
|
||||
|
||||
// 获取群主信息
|
||||
if group.OwnerID != "" {
|
||||
owner, err := s.userRepo.GetByID(group.OwnerID)
|
||||
if err == nil && owner != nil {
|
||||
response.Owner = &dto.UserResponse{
|
||||
ID: owner.ID,
|
||||
Username: owner.Username,
|
||||
Nickname: owner.Nickname,
|
||||
Avatar: owner.Avatar,
|
||||
CreatedAt: dto.FormatTime(owner.CreatedAt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取帖子数量
|
||||
postCount, _ := s.groupRepo.GetGroupPostCount(group.ID)
|
||||
response.PostCount = postCount
|
||||
|
||||
responses = append(responses, response)
|
||||
}
|
||||
|
||||
return responses, total, nil
|
||||
}
|
||||
|
||||
// GetGroupDetail 获取群组详情
|
||||
func (s *adminGroupServiceImpl) GetGroupDetail(ctx context.Context, groupID string) (*dto.AdminGroupDetailResponse, error) {
|
||||
group, err := s.groupRepo.GetByID(groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &dto.AdminGroupDetailResponse{
|
||||
ID: group.ID,
|
||||
Name: group.Name,
|
||||
Avatar: group.Avatar,
|
||||
Description: group.Description,
|
||||
OwnerID: group.OwnerID,
|
||||
MemberCount: group.MemberCount,
|
||||
MaxMembers: group.MaxMembers,
|
||||
JoinType: int(group.JoinType),
|
||||
MuteAll: group.MuteAll,
|
||||
Status: "active",
|
||||
IsPublic: true, // 默认为公开群组,后续可以根据需要添加字段
|
||||
CreatedAt: dto.FormatTime(group.CreatedAt),
|
||||
UpdatedAt: dto.FormatTime(group.UpdatedAt),
|
||||
}
|
||||
|
||||
// 获取群主信息
|
||||
if group.OwnerID != "" {
|
||||
owner, err := s.userRepo.GetByID(group.OwnerID)
|
||||
if err == nil && owner != nil {
|
||||
response.Owner = &dto.UserResponse{
|
||||
ID: owner.ID,
|
||||
Username: owner.Username,
|
||||
Nickname: owner.Nickname,
|
||||
Email: owner.Email,
|
||||
Phone: owner.Phone,
|
||||
Avatar: owner.Avatar,
|
||||
Bio: owner.Bio,
|
||||
Website: owner.Website,
|
||||
Location: owner.Location,
|
||||
CreatedAt: dto.FormatTime(owner.CreatedAt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取帖子数量
|
||||
postCount, _ := s.groupRepo.GetGroupPostCount(group.ID)
|
||||
response.PostCount = postCount
|
||||
|
||||
// 获取最新公告
|
||||
announcement, _ := s.groupRepo.GetLatestAnnouncement(groupID)
|
||||
if announcement != nil {
|
||||
response.Announcement = &dto.GroupAnnouncementResponse{
|
||||
ID: announcement.ID,
|
||||
GroupID: announcement.GroupID,
|
||||
Content: announcement.Content,
|
||||
AuthorID: announcement.AuthorID,
|
||||
IsPinned: announcement.IsPinned,
|
||||
CreatedAt: dto.FormatTime(announcement.CreatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetGroupMembers 获取群组成员列表
|
||||
func (s *adminGroupServiceImpl) GetGroupMembers(ctx context.Context, groupID string, page, pageSize int) (*dto.AdminGroupMemberListResponse, error) {
|
||||
// 先检查群组是否存在
|
||||
_, err := s.groupRepo.GetByID(groupID)
|
||||
if err != nil {
|
||||
return nil, errors.New("群组不存在")
|
||||
}
|
||||
|
||||
members, total, err := s.groupRepo.GetMembersWithUserInfo(groupID, page, pageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := make([]*dto.AdminGroupMemberResponse, 0, len(members))
|
||||
for _, member := range members {
|
||||
item := &dto.AdminGroupMemberResponse{
|
||||
ID: member.ID,
|
||||
UserID: member.UserID,
|
||||
Role: member.Role,
|
||||
Nickname: member.Nickname,
|
||||
Muted: member.Muted,
|
||||
JoinedAt: dto.FormatTime(member.JoinTime),
|
||||
}
|
||||
|
||||
if member.User != nil {
|
||||
item.Username = member.User.Username
|
||||
item.Nickname = member.User.Nickname
|
||||
item.Avatar = member.User.Avatar
|
||||
item.User = &dto.UserResponse{
|
||||
ID: member.User.ID,
|
||||
Username: member.User.Username,
|
||||
Nickname: member.User.Nickname,
|
||||
Avatar: member.User.Avatar,
|
||||
CreatedAt: dto.FormatTime(member.User.CreatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
return &dto.AdminGroupMemberListResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteGroup 解散群组
|
||||
func (s *adminGroupServiceImpl) DeleteGroup(ctx context.Context, groupID string) error {
|
||||
// 先检查群组是否存在
|
||||
_, err := s.groupRepo.GetByID(groupID)
|
||||
if err != nil {
|
||||
return errors.New("群组不存在")
|
||||
}
|
||||
|
||||
return s.groupRepo.Delete(groupID)
|
||||
}
|
||||
|
||||
// UpdateGroupStatus 更新群组状态
|
||||
func (s *adminGroupServiceImpl) UpdateGroupStatus(ctx context.Context, groupID string, status string) (*dto.AdminGroupDetailResponse, error) {
|
||||
// 先检查群组是否存在
|
||||
group, err := s.groupRepo.GetByID(groupID)
|
||||
if err != nil {
|
||||
return nil, errors.New("群组不存在")
|
||||
}
|
||||
|
||||
// 验证状态值
|
||||
if status != "active" && status != "dissolved" {
|
||||
return nil, errors.New("无效的状态值")
|
||||
}
|
||||
|
||||
// 如果状态是dissolved,则解散群组
|
||||
if status == "dissolved" {
|
||||
err = s.groupRepo.Delete(groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// 更新群组状态
|
||||
err = s.groupRepo.UpdateGroupStatus(groupID, status)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 重新获取群组信息
|
||||
group, err = s.groupRepo.GetByID(groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
response := &dto.AdminGroupDetailResponse{
|
||||
ID: group.ID,
|
||||
Name: group.Name,
|
||||
Avatar: group.Avatar,
|
||||
Description: group.Description,
|
||||
OwnerID: group.OwnerID,
|
||||
MemberCount: group.MemberCount,
|
||||
MaxMembers: group.MaxMembers,
|
||||
JoinType: int(group.JoinType),
|
||||
MuteAll: group.MuteAll,
|
||||
Status: status,
|
||||
IsPublic: true,
|
||||
CreatedAt: dto.FormatTime(group.CreatedAt),
|
||||
UpdatedAt: dto.FormatTime(group.UpdatedAt),
|
||||
}
|
||||
|
||||
// 获取群主信息
|
||||
if group.OwnerID != "" {
|
||||
owner, err := s.userRepo.GetByID(group.OwnerID)
|
||||
if err == nil && owner != nil {
|
||||
response.Owner = &dto.UserResponse{
|
||||
ID: owner.ID,
|
||||
Username: owner.Username,
|
||||
Nickname: owner.Nickname,
|
||||
Avatar: owner.Avatar,
|
||||
CreatedAt: dto.FormatTime(owner.CreatedAt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// RemoveMember 移除群组成员
|
||||
func (s *adminGroupServiceImpl) RemoveMember(ctx context.Context, groupID string, userID string) error {
|
||||
// 先检查群组是否存在
|
||||
_, err := s.groupRepo.GetByID(groupID)
|
||||
if err != nil {
|
||||
return errors.New("群组不存在")
|
||||
}
|
||||
|
||||
// 检查用户是否是群成员
|
||||
member, err := s.groupRepo.GetMember(groupID, userID)
|
||||
if err != nil {
|
||||
return errors.New("用户不是群组成员")
|
||||
}
|
||||
|
||||
// 不能移除群主
|
||||
if member.Role == model.GroupRoleOwner {
|
||||
return errors.New("不能移除群主")
|
||||
}
|
||||
|
||||
return s.groupRepo.RemoveMember(groupID, userID)
|
||||
}
|
||||
|
||||
// TransferOwner 转让群主
|
||||
func (s *adminGroupServiceImpl) TransferOwner(ctx context.Context, groupID string, newOwnerID string) (*dto.AdminGroupDetailResponse, error) {
|
||||
// 先检查群组是否存在
|
||||
group, err := s.groupRepo.GetByID(groupID)
|
||||
if err != nil {
|
||||
return nil, errors.New("群组不存在")
|
||||
}
|
||||
|
||||
// 检查新群主是否是群成员
|
||||
newOwnerMember, err := s.groupRepo.GetMember(groupID, newOwnerID)
|
||||
if err != nil {
|
||||
return nil, errors.New("新群主不是群组成员")
|
||||
}
|
||||
|
||||
// 获取当前群主信息
|
||||
currentOwnerMember, err := s.groupRepo.GetMember(groupID, group.OwnerID)
|
||||
if err != nil {
|
||||
return nil, errors.New("当前群主信息不存在")
|
||||
}
|
||||
|
||||
oldOwnerID := group.OwnerID
|
||||
|
||||
// 更新当前群主为普通成员
|
||||
err = s.groupRepo.SetMemberRole(groupID, oldOwnerID, model.GroupRoleMember)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新新群主角色
|
||||
err = s.groupRepo.SetMemberRole(groupID, newOwnerID, model.GroupRoleOwner)
|
||||
if err != nil {
|
||||
// 回滚
|
||||
_ = s.groupRepo.SetMemberRole(groupID, oldOwnerID, model.GroupRoleOwner)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新群组的OwnerID
|
||||
group.OwnerID = newOwnerID
|
||||
err = s.groupRepo.Update(group)
|
||||
if err != nil {
|
||||
// 回滚
|
||||
_ = s.groupRepo.SetMemberRole(groupID, oldOwnerID, model.GroupRoleOwner)
|
||||
_ = s.groupRepo.SetMemberRole(groupID, newOwnerID, newOwnerMember.Role)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 重新获取群组信息
|
||||
group, err = s.groupRepo.GetByID(groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
response := &dto.AdminGroupDetailResponse{
|
||||
ID: group.ID,
|
||||
Name: group.Name,
|
||||
Avatar: group.Avatar,
|
||||
Description: group.Description,
|
||||
OwnerID: group.OwnerID,
|
||||
MemberCount: group.MemberCount,
|
||||
MaxMembers: group.MaxMembers,
|
||||
JoinType: int(group.JoinType),
|
||||
MuteAll: group.MuteAll,
|
||||
Status: "active",
|
||||
IsPublic: true,
|
||||
CreatedAt: dto.FormatTime(group.CreatedAt),
|
||||
UpdatedAt: dto.FormatTime(group.UpdatedAt),
|
||||
}
|
||||
|
||||
// 获取新群主信息
|
||||
if group.OwnerID != "" {
|
||||
owner, err := s.userRepo.GetByID(group.OwnerID)
|
||||
if err == nil && owner != nil {
|
||||
response.Owner = &dto.UserResponse{
|
||||
ID: owner.ID,
|
||||
Username: owner.Username,
|
||||
Nickname: owner.Nickname,
|
||||
Avatar: owner.Avatar,
|
||||
CreatedAt: dto.FormatTime(owner.CreatedAt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = currentOwnerMember // 避免 unused variable 警告
|
||||
|
||||
return response, nil
|
||||
}
|
||||
341
internal/service/admin_post_service.go
Normal file
341
internal/service/admin_post_service.go
Normal file
@@ -0,0 +1,341 @@
|
||||
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)
|
||||
}
|
||||
|
||||
// adminPostServiceImpl 管理端帖子服务实现
|
||||
type adminPostServiceImpl struct {
|
||||
postRepo *repository.PostRepository
|
||||
cache cache.Cache
|
||||
}
|
||||
|
||||
// NewAdminPostService 创建管理端帖子服务
|
||||
func NewAdminPostService(postRepo *repository.PostRepository, cacheBackend cache.Cache) AdminPostService {
|
||||
return &adminPostServiceImpl{
|
||||
postRepo: postRepo,
|
||||
cache: cacheBackend,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
err := s.postRepo.Delete(postID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 失效缓存
|
||||
cache.InvalidatePostDetail(s.cache, postID)
|
||||
cache.InvalidatePostList(s.cache)
|
||||
|
||||
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.UserResponse{
|
||||
ID: post.User.ID,
|
||||
Username: post.User.Username,
|
||||
Nickname: post.User.Nickname,
|
||||
Email: post.User.Email,
|
||||
Phone: post.User.Phone,
|
||||
Avatar: post.User.Avatar,
|
||||
Bio: post.User.Bio,
|
||||
Website: post.User.Website,
|
||||
Location: post.User.Location,
|
||||
CreatedAt: post.User.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
}
|
||||
|
||||
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"),
|
||||
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.UserResponse{
|
||||
ID: post.User.ID,
|
||||
Username: post.User.Username,
|
||||
Nickname: post.User.Nickname,
|
||||
Email: post.User.Email,
|
||||
Phone: post.User.Phone,
|
||||
Avatar: post.User.Avatar,
|
||||
Bio: post.User.Bio,
|
||||
Website: post.User.Website,
|
||||
Location: post.User.Location,
|
||||
CreatedAt: post.User.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
}
|
||||
|
||||
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"),
|
||||
Author: author,
|
||||
}
|
||||
}
|
||||
145
internal/service/admin_user_service.go
Normal file
145
internal/service/admin_user_service.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/repository"
|
||||
)
|
||||
|
||||
// AdminUserService 管理端用户服务接口
|
||||
type AdminUserService interface {
|
||||
// GetUserList 获取用户列表(分页、搜索、状态筛选)
|
||||
GetUserList(ctx context.Context, page, pageSize int, keyword, status string) ([]dto.AdminUserListResponse, int64, error)
|
||||
// GetUserDetail 获取用户详情(含统计信息)
|
||||
GetUserDetail(ctx context.Context, userID string) (*dto.AdminUserDetailResponse, error)
|
||||
// UpdateUserStatus 更新用户状态
|
||||
UpdateUserStatus(ctx context.Context, userID string, status model.UserStatus) (*dto.AdminUserDetailResponse, error)
|
||||
}
|
||||
|
||||
// adminUserServiceImpl 管理端用户服务实现
|
||||
type adminUserServiceImpl struct {
|
||||
userRepo *repository.UserRepository
|
||||
}
|
||||
|
||||
// NewAdminUserService 创建管理端用户服务
|
||||
func NewAdminUserService(userRepo *repository.UserRepository) AdminUserService {
|
||||
return &adminUserServiceImpl{
|
||||
userRepo: userRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserList 获取用户列表
|
||||
func (s *adminUserServiceImpl) GetUserList(ctx context.Context, page, pageSize int, keyword, status string) ([]dto.AdminUserListResponse, int64, error) {
|
||||
users, total, err := s.userRepo.AdminList(page, pageSize, keyword, status)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
responses := make([]dto.AdminUserListResponse, len(users))
|
||||
for i, user := range users {
|
||||
responses[i] = convertUserToAdminListResponse(user)
|
||||
}
|
||||
|
||||
return responses, total, nil
|
||||
}
|
||||
|
||||
// GetUserDetail 获取用户详情
|
||||
func (s *adminUserServiceImpl) GetUserDetail(ctx context.Context, userID string) (*dto.AdminUserDetailResponse, error) {
|
||||
user, err := s.userRepo.GetByID(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取帖子数
|
||||
postsCount, _ := s.userRepo.GetPostsCount(userID)
|
||||
|
||||
// 获取评论数
|
||||
commentsCount, _ := s.userRepo.GetCommentsCount(userID)
|
||||
|
||||
return convertUserToAdminDetailResponse(user, postsCount, commentsCount), nil
|
||||
}
|
||||
|
||||
// UpdateUserStatus 更新用户状态
|
||||
func (s *adminUserServiceImpl) UpdateUserStatus(ctx context.Context, userID string, status model.UserStatus) (*dto.AdminUserDetailResponse, error) {
|
||||
// 先检查用户是否存在
|
||||
user, err := s.userRepo.GetByID(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
if err := s.userRepo.UpdateStatus(userID, status); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 重新获取用户信息
|
||||
user, err = s.userRepo.GetByID(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取统计信息
|
||||
postsCount, _ := s.userRepo.GetPostsCount(userID)
|
||||
commentsCount, _ := s.userRepo.GetCommentsCount(userID)
|
||||
|
||||
return convertUserToAdminDetailResponse(user, postsCount, commentsCount), nil
|
||||
}
|
||||
|
||||
// convertUserToAdminListResponse 转换用户为管理端列表响应
|
||||
func convertUserToAdminListResponse(user *model.User) dto.AdminUserListResponse {
|
||||
var lastLoginAt string
|
||||
if user.LastLoginAt != nil {
|
||||
lastLoginAt = user.LastLoginAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
return dto.AdminUserListResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Nickname: user.Nickname,
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
EmailVerified: user.EmailVerified,
|
||||
Avatar: user.Avatar,
|
||||
Status: string(user.Status),
|
||||
PostsCount: user.PostsCount,
|
||||
FollowersCount: user.FollowersCount,
|
||||
FollowingCount: user.FollowingCount,
|
||||
LastLoginAt: lastLoginAt,
|
||||
LastLoginIP: user.LastLoginIP,
|
||||
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
}
|
||||
|
||||
// convertUserToAdminDetailResponse 转换用户为管理端详情响应
|
||||
func convertUserToAdminDetailResponse(user *model.User, postsCount, commentsCount int64) *dto.AdminUserDetailResponse {
|
||||
var lastLoginAt string
|
||||
if user.LastLoginAt != nil {
|
||||
lastLoginAt = user.LastLoginAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
return &dto.AdminUserDetailResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Nickname: user.Nickname,
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
EmailVerified: user.EmailVerified,
|
||||
Avatar: user.Avatar,
|
||||
CoverURL: user.CoverURL,
|
||||
Bio: user.Bio,
|
||||
Website: user.Website,
|
||||
Location: user.Location,
|
||||
IsVerified: user.IsVerified,
|
||||
Status: string(user.Status),
|
||||
PostsCount: int(postsCount),
|
||||
CommentsCount: commentsCount,
|
||||
FollowersCount: user.FollowersCount,
|
||||
FollowingCount: user.FollowingCount,
|
||||
LastLoginAt: lastLoginAt,
|
||||
LastLoginIP: user.LastLoginIP,
|
||||
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"carrot_bbs/internal/config"
|
||||
"carrot_bbs/internal/repository"
|
||||
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v3"
|
||||
)
|
||||
|
||||
// CasbinService Casbin 权限服务接口
|
||||
@@ -43,6 +43,15 @@ type CasbinService interface {
|
||||
|
||||
// ClearCache 清除权限缓存
|
||||
ClearCache(ctx context.Context) error
|
||||
|
||||
// GetPermissionsForRole 获取角色的所有权限
|
||||
GetPermissionsForRole(ctx context.Context, role string) ([][]string, error)
|
||||
|
||||
// UpdatePermissionsForRole 更新角色的权限(先删除所有权限,再添加新权限)
|
||||
UpdatePermissionsForRole(ctx context.Context, role string, permissions [][]string) error
|
||||
|
||||
// GetAllPermissions 获取系统中所有权限定义
|
||||
GetAllPermissions(ctx context.Context) [][]string
|
||||
}
|
||||
|
||||
type casbinService struct {
|
||||
@@ -223,3 +232,34 @@ func (s *casbinService) clearUserCache(ctx context.Context, userID string) error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *casbinService) GetPermissionsForRole(ctx context.Context, role string) ([][]string, error) {
|
||||
return s.enforcer.GetPermissionsForUser(role)
|
||||
}
|
||||
|
||||
func (s *casbinService) UpdatePermissionsForRole(ctx context.Context, role string, permissions [][]string) error {
|
||||
// 先删除该角色的所有权限
|
||||
_, err := s.enforcer.DeletePermissionsForUser(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 添加新权限
|
||||
for _, perm := range permissions {
|
||||
if len(perm) >= 2 {
|
||||
resource := perm[0]
|
||||
action := perm[1]
|
||||
if _, err := s.enforcer.AddPolicy(role, resource, action); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
return s.ClearCache(ctx)
|
||||
}
|
||||
|
||||
func (s *casbinService) GetAllPermissions(ctx context.Context) [][]string {
|
||||
permissions, _ := s.enforcer.GetPolicy()
|
||||
return permissions
|
||||
}
|
||||
|
||||
@@ -24,6 +24,37 @@ type RoleService interface {
|
||||
|
||||
// GetRole 获取角色详情
|
||||
GetRole(ctx context.Context, name string) (*model.Role, error)
|
||||
|
||||
// GetRoleDetail 获取角色详情(包含用户数量和权限)
|
||||
GetRoleDetail(ctx context.Context, name string) (*RoleDetail, error)
|
||||
|
||||
// GetRolePermissions 获取角色的权限列表
|
||||
GetRolePermissions(ctx context.Context, name string) ([]Permission, error)
|
||||
|
||||
// GetAllPermissions 获取系统中所有权限定义
|
||||
GetAllPermissions(ctx context.Context) ([]Permission, error)
|
||||
|
||||
// UpdateRolePermissions 更新角色权限
|
||||
UpdateRolePermissions(ctx context.Context, roleName string, permissions []Permission) error
|
||||
}
|
||||
|
||||
// Permission 权限定义
|
||||
type Permission struct {
|
||||
Resource string `json:"resource"`
|
||||
Action string `json:"action"`
|
||||
}
|
||||
|
||||
// RoleDetail 角色详情
|
||||
type RoleDetail struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Description string `json:"description"`
|
||||
ParentRole *string `json:"parent_role"`
|
||||
Priority int `json:"priority"`
|
||||
UserCount int64 `json:"user_count"`
|
||||
Permissions []Permission `json:"permissions"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
type roleService struct {
|
||||
@@ -114,3 +145,106 @@ func (s *roleService) GetAllRoles(ctx context.Context) ([]model.Role, error) {
|
||||
func (s *roleService) GetRole(ctx context.Context, name string) (*model.Role, error) {
|
||||
return s.roleRepo.GetRole(ctx, name)
|
||||
}
|
||||
|
||||
func (s *roleService) GetRoleDetail(ctx context.Context, name string) (*RoleDetail, error) {
|
||||
role, err := s.roleRepo.GetRole(ctx, name)
|
||||
if err != nil {
|
||||
return nil, apperrors.ErrRoleNotFound
|
||||
}
|
||||
|
||||
// 获取用户数量
|
||||
userCount, err := s.roleRepo.GetRoleUserCount(ctx, name)
|
||||
if err != nil {
|
||||
userCount = 0
|
||||
}
|
||||
|
||||
// 获取权限
|
||||
permissions, err := s.casbinSvc.GetPermissionsForRole(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
perms := make([]Permission, 0)
|
||||
for _, p := range permissions {
|
||||
if len(p) >= 2 {
|
||||
perms = append(perms, Permission{
|
||||
Resource: p[0],
|
||||
Action: p[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return &RoleDetail{
|
||||
Name: role.Name,
|
||||
DisplayName: role.DisplayName,
|
||||
Description: role.Description,
|
||||
ParentRole: role.ParentRole,
|
||||
Priority: role.Priority,
|
||||
UserCount: userCount,
|
||||
Permissions: perms,
|
||||
CreatedAt: role.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
UpdatedAt: role.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *roleService) GetRolePermissions(ctx context.Context, name string) ([]Permission, error) {
|
||||
// 检查角色是否存在
|
||||
_, err := s.roleRepo.GetRole(ctx, name)
|
||||
if err != nil {
|
||||
return nil, apperrors.ErrRoleNotFound
|
||||
}
|
||||
|
||||
permissions, err := s.casbinSvc.GetPermissionsForRole(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 初始化为空数组,避免 JSON 序列化为 null
|
||||
perms := make([]Permission, 0)
|
||||
for _, p := range permissions {
|
||||
if len(p) >= 2 {
|
||||
perms = append(perms, Permission{
|
||||
Resource: p[0],
|
||||
Action: p[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
return perms, nil
|
||||
}
|
||||
|
||||
func (s *roleService) GetAllPermissions(ctx context.Context) ([]Permission, error) {
|
||||
permissions := s.casbinSvc.GetAllPermissions(ctx)
|
||||
|
||||
perms := make([]Permission, 0)
|
||||
for _, p := range permissions {
|
||||
if len(p) >= 3 {
|
||||
// p[0] 是角色名,p[1] 是资源,p[2] 是操作
|
||||
perms = append(perms, Permission{
|
||||
Resource: p[1],
|
||||
Action: p[2],
|
||||
})
|
||||
}
|
||||
}
|
||||
return perms, nil
|
||||
}
|
||||
|
||||
func (s *roleService) UpdateRolePermissions(ctx context.Context, roleName string, permissions []Permission) error {
|
||||
// 检查角色是否存在
|
||||
_, err := s.roleRepo.GetRole(ctx, roleName)
|
||||
if err != nil {
|
||||
return apperrors.ErrRoleNotFound
|
||||
}
|
||||
|
||||
// 不能修改超级管理员权限
|
||||
if roleName == model.RoleSuperAdmin {
|
||||
return apperrors.ErrCannotModifySuperAdmin
|
||||
}
|
||||
|
||||
// 转换权限格式
|
||||
var perms [][]string
|
||||
for _, p := range permissions {
|
||||
perms = append(perms, []string{p.Resource, p.Action})
|
||||
}
|
||||
|
||||
return s.casbinSvc.UpdatePermissionsForRole(ctx, roleName, perms)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user