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:
@@ -50,6 +50,56 @@ type UserDetailResponse struct {
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// ==================== Admin User DTOs ====================
|
||||
|
||||
// AdminUserListResponse 管理端用户列表响应
|
||||
type AdminUserListResponse struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Email *string `json:"email,omitempty"`
|
||||
Phone *string `json:"phone,omitempty"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
Avatar string `json:"avatar"`
|
||||
Status string `json:"status"`
|
||||
PostsCount int `json:"posts_count"`
|
||||
FollowersCount int `json:"followers_count"`
|
||||
FollowingCount int `json:"following_count"`
|
||||
LastLoginAt string `json:"last_login_at,omitempty"`
|
||||
LastLoginIP string `json:"last_login_ip,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// AdminUserDetailResponse 管理端用户详情响应
|
||||
type AdminUserDetailResponse struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Email *string `json:"email"`
|
||||
Phone *string `json:"phone"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
Avatar string `json:"avatar"`
|
||||
CoverURL string `json:"cover_url"`
|
||||
Bio string `json:"bio"`
|
||||
Website string `json:"website"`
|
||||
Location string `json:"location"`
|
||||
IsVerified bool `json:"is_verified"`
|
||||
Status string `json:"status"`
|
||||
PostsCount int `json:"posts_count"`
|
||||
CommentsCount int64 `json:"comments_count"`
|
||||
FollowersCount int `json:"followers_count"`
|
||||
FollowingCount int `json:"following_count"`
|
||||
LastLoginAt string `json:"last_login_at,omitempty"`
|
||||
LastLoginIP string `json:"last_login_ip,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// AdminUpdateUserStatusRequest 更新用户状态请求
|
||||
type AdminUpdateUserStatusRequest struct {
|
||||
Status string `json:"status" binding:"required,oneof=active banned"`
|
||||
}
|
||||
|
||||
// ==================== Post DTOs ====================
|
||||
|
||||
// PostImageResponse 帖子图片响应
|
||||
@@ -819,3 +869,293 @@ type WSResponse struct {
|
||||
Data interface{} `json:"data,omitempty"` // 响应数据
|
||||
Error string `json:"error,omitempty"` // 错误信息
|
||||
}
|
||||
|
||||
// ==================== Admin Post DTOs ====================
|
||||
|
||||
// AdminPostListResponse 管理端帖子列表响应
|
||||
type AdminPostListResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Images []PostImageResponse `json:"images"`
|
||||
Status string `json:"status"`
|
||||
LikesCount int `json:"likes_count"`
|
||||
CommentsCount int `json:"comments_count"`
|
||||
FavoritesCount int `json:"favorites_count"`
|
||||
ViewsCount int `json:"views_count"`
|
||||
IsPinned bool `json:"is_pinned"`
|
||||
IsFeatured bool `json:"is_featured"`
|
||||
IsLocked bool `json:"is_locked"`
|
||||
RejectReason string `json:"reject_reason,omitempty"`
|
||||
ReviewedAt string `json:"reviewed_at,omitempty"`
|
||||
ReviewedBy string `json:"reviewed_by,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Author *UserResponse `json:"author"`
|
||||
}
|
||||
|
||||
// AdminPostDetailResponse 管理端帖子详情响应
|
||||
type AdminPostDetailResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
CommunityID string `json:"community_id"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Images []PostImageResponse `json:"images"`
|
||||
Status string `json:"status"`
|
||||
LikesCount int `json:"likes_count"`
|
||||
CommentsCount int `json:"comments_count"`
|
||||
FavoritesCount int `json:"favorites_count"`
|
||||
SharesCount int `json:"shares_count"`
|
||||
ViewsCount int `json:"views_count"`
|
||||
HotScore float64 `json:"hot_score"`
|
||||
IsPinned bool `json:"is_pinned"`
|
||||
IsFeatured bool `json:"is_featured"`
|
||||
IsLocked bool `json:"is_locked"`
|
||||
IsVote bool `json:"is_vote"`
|
||||
RejectReason string `json:"reject_reason,omitempty"`
|
||||
ReviewedAt string `json:"reviewed_at,omitempty"`
|
||||
ReviewedBy string `json:"reviewed_by,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Author *UserResponse `json:"author"`
|
||||
}
|
||||
|
||||
// AdminModeratePostRequest 审核帖子请求
|
||||
type AdminModeratePostRequest struct {
|
||||
Status string `json:"status" binding:"required,oneof=published rejected"`
|
||||
Reason string `json:"reason"` // 拒绝理由(当status为rejected时)
|
||||
}
|
||||
|
||||
// AdminBatchDeletePostsRequest 批量删除帖子请求
|
||||
type AdminBatchDeletePostsRequest struct {
|
||||
IDs []string `json:"ids" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
// AdminBatchModeratePostsRequest 批量审核帖子请求
|
||||
type AdminBatchModeratePostsRequest struct {
|
||||
IDs []string `json:"ids" binding:"required,min=1"`
|
||||
Status string `json:"status" binding:"required,oneof=published rejected"`
|
||||
}
|
||||
|
||||
// AdminSetPostPinRequest 置顶帖子请求
|
||||
type AdminSetPostPinRequest struct {
|
||||
IsPinned bool `json:"is_pinned"`
|
||||
}
|
||||
|
||||
// AdminSetPostFeatureRequest 加精帖子请求
|
||||
type AdminSetPostFeatureRequest struct {
|
||||
IsFeatured bool `json:"is_featured"`
|
||||
}
|
||||
|
||||
// AdminBatchOperationResponse 批量操作响应
|
||||
type AdminBatchOperationResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Failed []string `json:"failed,omitempty"` // 失败的ID列表
|
||||
}
|
||||
|
||||
// ==================== Admin Comment DTOs ====================
|
||||
|
||||
// AdminCommentListResponse 管理端评论列表响应
|
||||
type AdminCommentListResponse struct {
|
||||
ID string `json:"id"`
|
||||
PostID string `json:"post_id"`
|
||||
UserID string `json:"user_id"`
|
||||
ParentID *string `json:"parent_id"`
|
||||
RootID *string `json:"root_id"`
|
||||
Content string `json:"content"`
|
||||
Images []CommentImageResponse `json:"images"`
|
||||
Status string `json:"status"`
|
||||
LikesCount int `json:"likes_count"`
|
||||
RepliesCount int `json:"replies_count"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Author *UserResponse `json:"author"`
|
||||
Post *AdminCommentPostInfo `json:"post"`
|
||||
}
|
||||
|
||||
// AdminCommentPostInfo 评论关联的帖子简要信息
|
||||
type AdminCommentPostInfo struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
// AdminCommentDetailResponse 管理端评论详情响应
|
||||
type AdminCommentDetailResponse struct {
|
||||
ID string `json:"id"`
|
||||
PostID string `json:"post_id"`
|
||||
UserID string `json:"user_id"`
|
||||
ParentID *string `json:"parent_id"`
|
||||
RootID *string `json:"root_id"`
|
||||
Content string `json:"content"`
|
||||
Images []CommentImageResponse `json:"images"`
|
||||
Status string `json:"status"`
|
||||
LikesCount int `json:"likes_count"`
|
||||
RepliesCount int `json:"replies_count"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Author *UserResponse `json:"author"`
|
||||
Post *AdminCommentPostInfo `json:"post"`
|
||||
ReplyToUser *UserResponse `json:"reply_to_user,omitempty"` // 被回复的用户信息
|
||||
}
|
||||
|
||||
// AdminBatchDeleteCommentsRequest 批量删除评论请求
|
||||
type AdminBatchDeleteCommentsRequest struct {
|
||||
IDs []string `json:"ids" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
// ==================== Admin Group DTOs ====================
|
||||
|
||||
// AdminGroupListResponse 管理端群组列表响应
|
||||
type AdminGroupListResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Avatar string `json:"avatar"`
|
||||
Description string `json:"description"`
|
||||
OwnerID string `json:"owner_id"`
|
||||
Owner *UserResponse `json:"owner"`
|
||||
MemberCount int `json:"member_count"`
|
||||
MaxMembers int `json:"max_members"`
|
||||
JoinType int `json:"join_type"`
|
||||
MuteAll bool `json:"mute_all"`
|
||||
Status string `json:"status"`
|
||||
PostCount int64 `json:"post_count"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// AdminGroupDetailResponse 管理端群组详情响应
|
||||
type AdminGroupDetailResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Avatar string `json:"avatar"`
|
||||
Description string `json:"description"`
|
||||
OwnerID string `json:"owner_id"`
|
||||
Owner *UserResponse `json:"owner"`
|
||||
MemberCount int `json:"member_count"`
|
||||
MaxMembers int `json:"max_members"`
|
||||
JoinType int `json:"join_type"`
|
||||
MuteAll bool `json:"mute_all"`
|
||||
Status string `json:"status"`
|
||||
PostCount int64 `json:"post_count"`
|
||||
Announcement *GroupAnnouncementResponse `json:"announcement,omitempty"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// AdminGroupMemberResponse 管理端群组成员响应
|
||||
type AdminGroupMemberResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Role string `json:"role"`
|
||||
Muted bool `json:"muted"`
|
||||
JoinedAt string `json:"joined_at"`
|
||||
User *UserResponse `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
// AdminGroupMemberListResponse 管理端群组成员列表响应
|
||||
type AdminGroupMemberListResponse struct {
|
||||
List []*AdminGroupMemberResponse `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
// AdminUpdateGroupStatusRequest 更新群组状态请求
|
||||
type AdminUpdateGroupStatusRequest struct {
|
||||
Status string `json:"status" binding:"required,oneof=active dissolved"`
|
||||
}
|
||||
|
||||
// AdminTransferGroupOwnerRequest 转让群主请求
|
||||
type AdminTransferGroupOwnerRequest struct {
|
||||
NewOwnerID string `json:"new_owner_id" binding:"required"`
|
||||
}
|
||||
|
||||
// ==================== Admin Dashboard DTOs ====================
|
||||
|
||||
// DashboardStatsResponse 仪表盘统计数据响应
|
||||
type DashboardStatsResponse struct {
|
||||
TotalUsers int64 `json:"total_users"` // 总用户数
|
||||
TodayNewUsers int64 `json:"today_new_users"` // 今日新增用户
|
||||
ActiveUsersToday int64 `json:"active_users_today"` // 今日活跃用户 (DAU)
|
||||
ActiveUsersWeek int64 `json:"active_users_week"` // 本周活跃用户 (WAU)
|
||||
ActiveUsersMonth int64 `json:"active_users_month"` // 本月活跃用户 (MAU)
|
||||
Retention1Day int64 `json:"retention_1_day"` // 次日留存率 (百分比 0-100)
|
||||
Retention7Day int64 `json:"retention_7_day"` // 7日留存率 (百分比 0-100)
|
||||
Retention30Day int64 `json:"retention_30_day"` // 30日留存率 (百分比 0-100)
|
||||
TotalPosts int64 `json:"total_posts"` // 总帖子数
|
||||
TodayPosts int64 `json:"today_posts"` // 今日帖子数
|
||||
PendingPosts int64 `json:"pending_posts"` // 待审核帖子数
|
||||
TotalComments int64 `json:"total_comments"` // 总评论数
|
||||
TodayComments int64 `json:"today_comments"` // 今日评论数
|
||||
TotalGroups int64 `json:"total_groups"` // 总群组数
|
||||
TodayNewGroups int64 `json:"today_new_groups"` // 今日新建群组
|
||||
PendingReview int64 `json:"pending_review"` // 待审核总数
|
||||
PendingPostsCount int64 `json:"pending_posts_count"` // 待审核帖子数
|
||||
PendingComments int64 `json:"pending_comments_count"` // 待审核评论数
|
||||
}
|
||||
|
||||
// UserActivityTrendResponse 用户活跃度趋势响应
|
||||
type UserActivityTrendResponse struct {
|
||||
Date string `json:"date"` // 日期
|
||||
NewUsers int64 `json:"new_users"` // 新增用户数
|
||||
ActiveUsers int64 `json:"active_users"` // 活跃用户数
|
||||
Posts int64 `json:"posts"` // 帖子数
|
||||
Comments int64 `json:"comments"` // 评论数
|
||||
}
|
||||
|
||||
// ContentStatsResponse 内容统计响应
|
||||
type ContentStatsResponse struct {
|
||||
ContentDistribution []ContentDistributionItem `json:"content_distribution"` // 内容分布
|
||||
PostStatusDistribution []PostStatusDistributionItem `json:"post_status_distribution"` // 帖子状态分布
|
||||
UserGrowth []UserGrowthItem `json:"user_growth"` // 用户增长趋势
|
||||
}
|
||||
|
||||
// ContentDistributionItem 内容分布项
|
||||
type ContentDistributionItem struct {
|
||||
Type string `json:"type"` // 类型:posts, comments, groups
|
||||
Name string `json:"name"` // 名称
|
||||
Count int64 `json:"count"` // 数量
|
||||
}
|
||||
|
||||
// PostStatusDistributionItem 帖子状态分布项
|
||||
type PostStatusDistributionItem struct {
|
||||
Status string `json:"status"` // 状态
|
||||
Name string `json:"name"` // 名称
|
||||
Count int64 `json:"count"` // 数量
|
||||
}
|
||||
|
||||
// UserGrowthItem 用户增长项
|
||||
type UserGrowthItem struct {
|
||||
Date string `json:"date"` // 日期
|
||||
NewUsers int64 `json:"new_users"` // 新增用户数
|
||||
}
|
||||
|
||||
// PendingContentResponse 待审核内容响应
|
||||
type PendingContentResponse struct {
|
||||
ID string `json:"id"` // 内容ID
|
||||
Type string `json:"type"` // 内容类型(post/comment)
|
||||
Title string `json:"title"` // 标题(帖子有,评论可能为空)
|
||||
Content string `json:"content"` // 内容
|
||||
Author *PendingContentAuthor `json:"author"` // 作者信息
|
||||
CreatedAt string `json:"created_at"` // 创建时间
|
||||
}
|
||||
|
||||
// PendingContentAuthor 待审核内容作者信息
|
||||
type PendingContentAuthor struct {
|
||||
ID string `json:"id"` // 用户ID
|
||||
Username string `json:"username"` // 用户名
|
||||
Nickname string `json:"nickname"` // 昵称
|
||||
Avatar string `json:"avatar"` // 头像
|
||||
}
|
||||
|
||||
// OnlineUsersResponse 在线用户数响应
|
||||
type OnlineUsersResponse struct {
|
||||
Count int64 `json:"count"` // 在线用户数
|
||||
}
|
||||
|
||||
151
internal/handler/admin_comment_handler.go
Normal file
151
internal/handler/admin_comment_handler.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminCommentHandler 管理端评论处理器
|
||||
type AdminCommentHandler struct {
|
||||
adminCommentService service.AdminCommentService
|
||||
}
|
||||
|
||||
// NewAdminCommentHandler 创建管理端评论处理器
|
||||
func NewAdminCommentHandler(adminCommentService service.AdminCommentService) *AdminCommentHandler {
|
||||
return &AdminCommentHandler{
|
||||
adminCommentService: adminCommentService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCommentList 获取评论列表
|
||||
// GET /api/v1/admin/comments
|
||||
func (h *AdminCommentHandler) GetCommentList(c *gin.Context) {
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
// 获取筛选参数
|
||||
keyword := c.Query("keyword")
|
||||
postID := c.Query("post_id")
|
||||
authorID := c.Query("author_id")
|
||||
status := c.Query("status")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
// 验证状态参数
|
||||
if status != "" && status != string(model.CommentStatusDraft) &&
|
||||
status != string(model.CommentStatusPending) &&
|
||||
status != string(model.CommentStatusPublished) &&
|
||||
status != string(model.CommentStatusRejected) &&
|
||||
status != string(model.CommentStatusDeleted) {
|
||||
response.BadRequest(c, "invalid status value")
|
||||
return
|
||||
}
|
||||
|
||||
comments, total, err := h.adminCommentService.GetCommentList(c.Request.Context(), page, pageSize, keyword, postID, authorID, status, startDate, endDate)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get comment list")
|
||||
return
|
||||
}
|
||||
|
||||
response.Paginated(c, comments, total, page, pageSize)
|
||||
}
|
||||
|
||||
// GetCommentDetail 获取评论详情
|
||||
// GET /api/v1/admin/comments/:id
|
||||
func (h *AdminCommentHandler) GetCommentDetail(c *gin.Context) {
|
||||
commentID := c.Param("id")
|
||||
if commentID == "" {
|
||||
response.BadRequest(c, "comment id is required")
|
||||
return
|
||||
}
|
||||
|
||||
commentDetail, err := h.adminCommentService.GetCommentDetail(c.Request.Context(), commentID)
|
||||
if err != nil {
|
||||
response.NotFound(c, "comment not found")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, commentDetail)
|
||||
}
|
||||
|
||||
// DeleteComment 删除评论
|
||||
// DELETE /api/v1/admin/comments/:id
|
||||
func (h *AdminCommentHandler) DeleteComment(c *gin.Context) {
|
||||
commentID := c.Param("id")
|
||||
if commentID == "" {
|
||||
response.BadRequest(c, "comment id is required")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.adminCommentService.DeleteComment(c.Request.Context(), commentID)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to delete comment")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dto.SuccessResponse{
|
||||
Success: true,
|
||||
Message: "comment deleted successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// BatchDeleteComments 批量删除评论
|
||||
// POST /api/v1/admin/comments/batch-delete
|
||||
func (h *AdminCommentHandler) BatchDeleteComments(c *gin.Context) {
|
||||
var req dto.AdminBatchDeleteCommentsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.adminCommentService.BatchDeleteComments(c.Request.Context(), req.IDs)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to batch delete comments")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
// GetPostComments 获取帖子的评论列表
|
||||
// GET /api/v1/admin/posts/:postId/comments
|
||||
func (h *AdminCommentHandler) GetPostComments(c *gin.Context) {
|
||||
postID := c.Param("postId")
|
||||
if postID == "" {
|
||||
response.BadRequest(c, "post id is required")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
comments, total, err := h.adminCommentService.GetCommentsByPostID(c.Request.Context(), postID, page, pageSize)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get post comments")
|
||||
return
|
||||
}
|
||||
|
||||
response.Paginated(c, comments, total, page, pageSize)
|
||||
}
|
||||
100
internal/handler/admin_dashboard_handler.go
Normal file
100
internal/handler/admin_dashboard_handler.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminDashboardHandler 管理端仪表盘处理器
|
||||
type AdminDashboardHandler struct {
|
||||
dashboardService service.AdminDashboardService
|
||||
}
|
||||
|
||||
// NewAdminDashboardHandler 创建管理端仪表盘处理器
|
||||
func NewAdminDashboardHandler(dashboardService service.AdminDashboardService) *AdminDashboardHandler {
|
||||
return &AdminDashboardHandler{
|
||||
dashboardService: dashboardService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetStats 获取统计数据
|
||||
// GET /api/v1/admin/dashboard/stats
|
||||
func (h *AdminDashboardHandler) GetStats(c *gin.Context) {
|
||||
stats, err := h.dashboardService.GetStats(c.Request.Context())
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get dashboard stats")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, stats)
|
||||
}
|
||||
|
||||
// GetUserActivityTrend 获取用户活跃度趋势
|
||||
// GET /api/v1/admin/dashboard/user-activity
|
||||
func (h *AdminDashboardHandler) GetUserActivityTrend(c *gin.Context) {
|
||||
// 解析天数参数,默认7天
|
||||
days, _ := strconv.Atoi(c.DefaultQuery("days", "7"))
|
||||
if days <= 0 {
|
||||
days = 7
|
||||
}
|
||||
if days > 30 {
|
||||
days = 30
|
||||
}
|
||||
|
||||
trend, err := h.dashboardService.GetUserActivityTrend(c.Request.Context(), days)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get user activity trend")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, trend)
|
||||
}
|
||||
|
||||
// GetContentStats 获取内容统计
|
||||
// GET /api/v1/admin/dashboard/content-stats
|
||||
func (h *AdminDashboardHandler) GetContentStats(c *gin.Context) {
|
||||
stats, err := h.dashboardService.GetContentStats(c.Request.Context())
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get content stats")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, stats)
|
||||
}
|
||||
|
||||
// GetPendingContent 获取待审核内容
|
||||
// GET /api/v1/admin/dashboard/pending-content
|
||||
func (h *AdminDashboardHandler) GetPendingContent(c *gin.Context) {
|
||||
// 解析limit参数,默认10条
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
if limit > 50 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
content, err := h.dashboardService.GetPendingContent(c.Request.Context(), limit)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get pending content")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, content)
|
||||
}
|
||||
|
||||
// GetOnlineUsers 获取在线用户数
|
||||
// GET /api/v1/admin/dashboard/online-users
|
||||
func (h *AdminDashboardHandler) GetOnlineUsers(c *gin.Context) {
|
||||
onlineUsers, err := h.dashboardService.GetOnlineUsers(c.Request.Context())
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get online users count")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, onlineUsers)
|
||||
}
|
||||
202
internal/handler/admin_group_handler.go
Normal file
202
internal/handler/admin_group_handler.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminGroupHandler 管理端群组处理器
|
||||
type AdminGroupHandler struct {
|
||||
adminGroupService service.AdminGroupService
|
||||
}
|
||||
|
||||
// NewAdminGroupHandler 创建管理端群组处理器
|
||||
func NewAdminGroupHandler(adminGroupService service.AdminGroupService) *AdminGroupHandler {
|
||||
return &AdminGroupHandler{
|
||||
adminGroupService: adminGroupService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetGroupList 获取群组列表
|
||||
// GET /api/v1/admin/groups
|
||||
func (h *AdminGroupHandler) GetGroupList(c *gin.Context) {
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
// 获取筛选参数
|
||||
keyword := c.Query("keyword")
|
||||
status := c.Query("status")
|
||||
ownerID := c.Query("owner_id")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
groups, total, err := h.adminGroupService.GetGroupList(c.Request.Context(), page, pageSize, keyword, status, ownerID, startDate, endDate)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get group list")
|
||||
return
|
||||
}
|
||||
|
||||
response.Paginated(c, groups, total, page, pageSize)
|
||||
}
|
||||
|
||||
// GetGroupDetail 获取群组详情
|
||||
// GET /api/v1/admin/groups/:id
|
||||
func (h *AdminGroupHandler) GetGroupDetail(c *gin.Context) {
|
||||
groupID := c.Param("id")
|
||||
if groupID == "" {
|
||||
response.BadRequest(c, "group id is required")
|
||||
return
|
||||
}
|
||||
|
||||
groupDetail, err := h.adminGroupService.GetGroupDetail(c.Request.Context(), groupID)
|
||||
if err != nil {
|
||||
response.NotFound(c, "group not found")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, groupDetail)
|
||||
}
|
||||
|
||||
// GetGroupMembers 获取群组成员列表
|
||||
// GET /api/v1/admin/groups/:id/members
|
||||
func (h *AdminGroupHandler) GetGroupMembers(c *gin.Context) {
|
||||
groupID := c.Param("id")
|
||||
if groupID == "" {
|
||||
response.BadRequest(c, "group id is required")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
members, err := h.adminGroupService.GetGroupMembers(c.Request.Context(), groupID, page, pageSize)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to get group members")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, members)
|
||||
}
|
||||
|
||||
// DeleteGroup 解散群组
|
||||
// DELETE /api/v1/admin/groups/:id
|
||||
func (h *AdminGroupHandler) DeleteGroup(c *gin.Context) {
|
||||
groupID := c.Param("id")
|
||||
if groupID == "" {
|
||||
response.BadRequest(c, "group id is required")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.adminGroupService.DeleteGroup(c.Request.Context(), groupID)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to delete group")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dto.SuccessResponse{
|
||||
Success: true,
|
||||
Message: "group deleted successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateGroupStatus 更新群组状态
|
||||
// PUT /api/v1/admin/groups/:id/status
|
||||
func (h *AdminGroupHandler) UpdateGroupStatus(c *gin.Context) {
|
||||
groupID := c.Param("id")
|
||||
if groupID == "" {
|
||||
response.BadRequest(c, "group id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AdminUpdateGroupStatusRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 验证状态值
|
||||
if req.Status != "active" && req.Status != "dissolved" {
|
||||
response.BadRequest(c, "invalid status value, must be 'active' or 'dissolved'")
|
||||
return
|
||||
}
|
||||
|
||||
groupDetail, err := h.adminGroupService.UpdateGroupStatus(c.Request.Context(), groupID, req.Status)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to update group status")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, groupDetail)
|
||||
}
|
||||
|
||||
// RemoveMember 移除群组成员
|
||||
// DELETE /api/v1/admin/groups/:id/members/:userId
|
||||
func (h *AdminGroupHandler) RemoveMember(c *gin.Context) {
|
||||
groupID := c.Param("id")
|
||||
userID := c.Param("userId")
|
||||
|
||||
if groupID == "" {
|
||||
response.BadRequest(c, "group id is required")
|
||||
return
|
||||
}
|
||||
if userID == "" {
|
||||
response.BadRequest(c, "user id is required")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.adminGroupService.RemoveMember(c.Request.Context(), groupID, userID)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to remove member")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dto.SuccessResponse{
|
||||
Success: true,
|
||||
Message: "member removed successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// TransferOwner 转让群主
|
||||
// PUT /api/v1/admin/groups/:id/transfer
|
||||
func (h *AdminGroupHandler) TransferOwner(c *gin.Context) {
|
||||
groupID := c.Param("id")
|
||||
if groupID == "" {
|
||||
response.BadRequest(c, "group id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AdminTransferGroupOwnerRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
groupDetail, err := h.adminGroupService.TransferOwner(c.Request.Context(), groupID, req.NewOwnerID)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to transfer owner")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, groupDetail)
|
||||
}
|
||||
228
internal/handler/admin_post_handler.go
Normal file
228
internal/handler/admin_post_handler.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminPostHandler 管理端帖子处理器
|
||||
type AdminPostHandler struct {
|
||||
adminPostService service.AdminPostService
|
||||
}
|
||||
|
||||
// NewAdminPostHandler 创建管理端帖子处理器
|
||||
func NewAdminPostHandler(adminPostService service.AdminPostService) *AdminPostHandler {
|
||||
return &AdminPostHandler{
|
||||
adminPostService: adminPostService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPostList 获取帖子列表
|
||||
// GET /api/v1/admin/posts
|
||||
func (h *AdminPostHandler) GetPostList(c *gin.Context) {
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
// 获取筛选参数
|
||||
keyword := c.Query("keyword")
|
||||
status := c.Query("status")
|
||||
authorID := c.Query("author_id")
|
||||
startDate := c.Query("start_date")
|
||||
endDate := c.Query("end_date")
|
||||
|
||||
// 验证状态参数
|
||||
if status != "" && status != string(model.PostStatusDraft) &&
|
||||
status != string(model.PostStatusPending) &&
|
||||
status != string(model.PostStatusPublished) &&
|
||||
status != string(model.PostStatusRejected) {
|
||||
response.BadRequest(c, "invalid status value")
|
||||
return
|
||||
}
|
||||
|
||||
posts, total, err := h.adminPostService.GetPostList(c.Request.Context(), page, pageSize, keyword, status, authorID, startDate, endDate)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get post list")
|
||||
return
|
||||
}
|
||||
|
||||
response.Paginated(c, posts, total, page, pageSize)
|
||||
}
|
||||
|
||||
// GetPostDetail 获取帖子详情
|
||||
// GET /api/v1/admin/posts/:id
|
||||
func (h *AdminPostHandler) GetPostDetail(c *gin.Context) {
|
||||
postID := c.Param("id")
|
||||
if postID == "" {
|
||||
response.BadRequest(c, "post id is required")
|
||||
return
|
||||
}
|
||||
|
||||
postDetail, err := h.adminPostService.GetPostDetail(c.Request.Context(), postID)
|
||||
if err != nil {
|
||||
response.NotFound(c, "post not found")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, postDetail)
|
||||
}
|
||||
|
||||
// DeletePost 删除帖子
|
||||
// DELETE /api/v1/admin/posts/:id
|
||||
func (h *AdminPostHandler) DeletePost(c *gin.Context) {
|
||||
postID := c.Param("id")
|
||||
if postID == "" {
|
||||
response.BadRequest(c, "post id is required")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.adminPostService.DeletePost(c.Request.Context(), postID)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to delete post")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, dto.SuccessResponse{
|
||||
Success: true,
|
||||
Message: "post deleted successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// ModeratePost 审核帖子
|
||||
// PUT /api/v1/admin/posts/:id/moderate
|
||||
func (h *AdminPostHandler) ModeratePost(c *gin.Context) {
|
||||
postID := c.Param("id")
|
||||
if postID == "" {
|
||||
response.BadRequest(c, "post id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AdminModeratePostRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 验证状态值
|
||||
if req.Status != string(model.PostStatusPublished) && req.Status != string(model.PostStatusRejected) {
|
||||
response.BadRequest(c, "invalid status value, must be 'published' or 'rejected'")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前用户ID作为审核人
|
||||
reviewedBy := c.GetString("user_id")
|
||||
|
||||
postDetail, err := h.adminPostService.ModeratePost(c.Request.Context(), postID, model.PostStatus(req.Status), req.Reason, reviewedBy)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to moderate post")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, postDetail)
|
||||
}
|
||||
|
||||
// BatchDeletePosts 批量删除帖子
|
||||
// POST /api/v1/admin/posts/batch-delete
|
||||
func (h *AdminPostHandler) BatchDeletePosts(c *gin.Context) {
|
||||
var req dto.AdminBatchDeletePostsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.adminPostService.BatchDeletePosts(c.Request.Context(), req.IDs)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to batch delete posts")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
// BatchModeratePosts 批量审核帖子
|
||||
// POST /api/v1/admin/posts/batch-moderate
|
||||
func (h *AdminPostHandler) BatchModeratePosts(c *gin.Context) {
|
||||
var req dto.AdminBatchModeratePostsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 验证状态值
|
||||
if req.Status != string(model.PostStatusPublished) && req.Status != string(model.PostStatusRejected) {
|
||||
response.BadRequest(c, "invalid status value, must be 'published' or 'rejected'")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前用户ID作为审核人
|
||||
reviewedBy := c.GetString("user_id")
|
||||
|
||||
result, err := h.adminPostService.BatchModeratePosts(c.Request.Context(), req.IDs, model.PostStatus(req.Status), reviewedBy)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to batch moderate posts")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
// SetPostPin 置顶/取消置顶帖子
|
||||
// PUT /api/v1/admin/posts/:id/pin
|
||||
func (h *AdminPostHandler) SetPostPin(c *gin.Context) {
|
||||
postID := c.Param("id")
|
||||
if postID == "" {
|
||||
response.BadRequest(c, "post id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AdminSetPostPinRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
postDetail, err := h.adminPostService.SetPostPin(c.Request.Context(), postID, req.IsPinned)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to set post pin status")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, postDetail)
|
||||
}
|
||||
|
||||
// SetPostFeature 加精/取消加精帖子
|
||||
// PUT /api/v1/admin/posts/:id/feature
|
||||
func (h *AdminPostHandler) SetPostFeature(c *gin.Context) {
|
||||
postID := c.Param("id")
|
||||
if postID == "" {
|
||||
response.BadRequest(c, "post id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AdminSetPostFeatureRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
postDetail, err := h.adminPostService.SetPostFeature(c.Request.Context(), postID, req.IsFeatured)
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to set post feature status")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, postDetail)
|
||||
}
|
||||
105
internal/handler/admin_user_handler.go
Normal file
105
internal/handler/admin_user_handler.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/response"
|
||||
"carrot_bbs/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminUserHandler 管理端用户处理器
|
||||
type AdminUserHandler struct {
|
||||
adminUserService service.AdminUserService
|
||||
}
|
||||
|
||||
// NewAdminUserHandler 创建管理端用户处理器
|
||||
func NewAdminUserHandler(adminUserService service.AdminUserService) *AdminUserHandler {
|
||||
return &AdminUserHandler{
|
||||
adminUserService: adminUserService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserList 获取用户列表
|
||||
// GET /api/v1/admin/users
|
||||
func (h *AdminUserHandler) GetUserList(c *gin.Context) {
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
// 获取筛选参数
|
||||
keyword := c.Query("keyword")
|
||||
status := c.Query("status")
|
||||
|
||||
// 验证状态参数
|
||||
if status != "" && status != string(model.UserStatusActive) && status != string(model.UserStatusBanned) {
|
||||
response.BadRequest(c, "invalid status value")
|
||||
return
|
||||
}
|
||||
|
||||
users, total, err := h.adminUserService.GetUserList(c.Request.Context(), page, pageSize, keyword, status)
|
||||
if err != nil {
|
||||
response.InternalServerError(c, "failed to get user list")
|
||||
return
|
||||
}
|
||||
|
||||
response.Paginated(c, users, total, page, pageSize)
|
||||
}
|
||||
|
||||
// GetUserDetail 获取用户详情
|
||||
// GET /api/v1/admin/users/:id
|
||||
func (h *AdminUserHandler) GetUserDetail(c *gin.Context) {
|
||||
userID := c.Param("id")
|
||||
if userID == "" {
|
||||
response.BadRequest(c, "user id is required")
|
||||
return
|
||||
}
|
||||
|
||||
userDetail, err := h.adminUserService.GetUserDetail(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
response.NotFound(c, "user not found")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, userDetail)
|
||||
}
|
||||
|
||||
// UpdateUserStatus 更新用户状态
|
||||
// PUT /api/v1/admin/users/:id/status
|
||||
func (h *AdminUserHandler) UpdateUserStatus(c *gin.Context) {
|
||||
userID := c.Param("id")
|
||||
if userID == "" {
|
||||
response.BadRequest(c, "user id is required")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AdminUpdateUserStatusRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 验证状态值
|
||||
if req.Status != string(model.UserStatusActive) && req.Status != string(model.UserStatusBanned) {
|
||||
response.BadRequest(c, "invalid status value, must be 'active' or 'banned'")
|
||||
return
|
||||
}
|
||||
|
||||
userDetail, err := h.adminUserService.UpdateUserStatus(c.Request.Context(), userID, model.UserStatus(req.Status))
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to update user status")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, userDetail)
|
||||
}
|
||||
@@ -202,3 +202,128 @@ func (h *RoleHandler) handleError(c *gin.Context, err error) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetRoleDetail 获取角色详情
|
||||
// GET /api/v1/admin/roles/:name
|
||||
func (h *RoleHandler) GetRoleDetail(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
if name == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": "BAD_REQUEST",
|
||||
"message": "缺少角色名称",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
detail, err := h.roleService.GetRoleDetail(c.Request.Context(), name)
|
||||
if err != nil {
|
||||
h.handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": detail,
|
||||
})
|
||||
}
|
||||
|
||||
// GetRolePermissions 获取角色权限
|
||||
// GET /api/v1/admin/roles/:name/permissions
|
||||
func (h *RoleHandler) GetRolePermissions(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
if name == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": "BAD_REQUEST",
|
||||
"message": "缺少角色名称",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
permissions, err := h.roleService.GetRolePermissions(c.Request.Context(), name)
|
||||
if err != nil {
|
||||
h.handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": permissions,
|
||||
})
|
||||
}
|
||||
|
||||
// GetAllPermissions 获取所有权限定义
|
||||
// GET /api/v1/admin/permissions
|
||||
func (h *RoleHandler) GetAllPermissions(c *gin.Context) {
|
||||
permissions, err := h.roleService.GetAllPermissions(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": "INTERNAL_ERROR",
|
||||
"message": "获取权限列表失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": permissions,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateRolePermissionsRequest 更新角色权限请求
|
||||
type UpdateRolePermissionsRequest struct {
|
||||
Permissions []PermissionInput `json:"permissions" binding:"required"`
|
||||
}
|
||||
|
||||
// PermissionInput 权限输入
|
||||
type PermissionInput struct {
|
||||
Resource string `json:"resource" binding:"required"`
|
||||
Action string `json:"action" binding:"required"`
|
||||
}
|
||||
|
||||
// UpdateRolePermissions 更新角色权限
|
||||
// PUT /api/v1/admin/roles/:name/permissions
|
||||
func (h *RoleHandler) UpdateRolePermissions(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
if name == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": "BAD_REQUEST",
|
||||
"message": "缺少角色名称",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateRolePermissionsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": "BAD_REQUEST",
|
||||
"message": "请求参数错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 转换权限格式
|
||||
var permissions []service.Permission
|
||||
for _, p := range req.Permissions {
|
||||
permissions = append(permissions, service.Permission{
|
||||
Resource: p.Resource,
|
||||
Action: p.Action,
|
||||
})
|
||||
}
|
||||
|
||||
err := h.roleService.UpdateRolePermissions(c.Request.Context(), name, permissions)
|
||||
if err != nil {
|
||||
h.handleError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 返回更新后的角色信息
|
||||
detail, err := h.roleService.GetRoleDetail(c.Request.Context(), name)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "权限更新成功",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "权限更新成功",
|
||||
"data": detail,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -155,6 +155,128 @@ func autoMigrate(db *gorm.DB) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化角色种子数据
|
||||
if err := seedRoles(db); err != nil {
|
||||
return fmt.Errorf("failed to seed roles: %w", err)
|
||||
}
|
||||
|
||||
// 初始化权限策略种子数据
|
||||
if err := seedPermissions(db); err != nil {
|
||||
return fmt.Errorf("failed to seed permissions: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// seedRoles 初始化角色种子数据
|
||||
func seedRoles(db *gorm.DB) error {
|
||||
// 检查是否已有角色数据
|
||||
var count int64
|
||||
if err := db.Model(&Role{}).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果已有数据,跳过种子初始化
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 预定义角色数据
|
||||
roles := []Role{
|
||||
{
|
||||
Name: RoleSuperAdmin,
|
||||
DisplayName: "超级管理员",
|
||||
Description: "拥有系统最高权限,可以管理所有用户和内容",
|
||||
Priority: 100,
|
||||
},
|
||||
{
|
||||
Name: RoleAdmin,
|
||||
DisplayName: "管理员",
|
||||
Description: "系统管理员,可以管理用户和内容",
|
||||
Priority: 80,
|
||||
},
|
||||
{
|
||||
Name: RoleModerator,
|
||||
DisplayName: "版主",
|
||||
Description: "内容审核员,可以审核和管理内容",
|
||||
Priority: 60,
|
||||
},
|
||||
{
|
||||
Name: RoleUser,
|
||||
DisplayName: "普通用户",
|
||||
Description: "注册用户,拥有基本权限",
|
||||
Priority: 40,
|
||||
},
|
||||
{
|
||||
Name: RoleBanned,
|
||||
DisplayName: "被封禁用户",
|
||||
Description: "被禁止访问的用户",
|
||||
Priority: 0,
|
||||
},
|
||||
}
|
||||
|
||||
// 批量插入角色
|
||||
if err := db.Create(&roles).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Seeded %d roles successfully", len(roles))
|
||||
return nil
|
||||
}
|
||||
|
||||
// seedPermissions 初始化权限策略种子数据
|
||||
func seedPermissions(db *gorm.DB) error {
|
||||
// 检查是否已有权限策略数据
|
||||
var count int64
|
||||
if err := db.Model(&CasbinRule{}).Where("ptype = ?", "p").Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果已有数据,跳过种子初始化
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 预定义权限策略数据
|
||||
// p = sub, obj, act (角色, 资源, 操作)
|
||||
permissions := []CasbinRule{
|
||||
// 超级管理员 - 拥有所有权限
|
||||
{Ptype: "p", V0: RoleSuperAdmin, V1: "/*", V2: "*"},
|
||||
|
||||
// 管理员 - 拥有管理权限
|
||||
{Ptype: "p", V0: RoleAdmin, V1: "/api/v1/admin/*", V2: "*"},
|
||||
{Ptype: "p", V0: RoleAdmin, V1: "/api/v1/users/*", V2: "GET"},
|
||||
{Ptype: "p", V0: RoleAdmin, V1: "/api/v1/posts/*", V2: "*"},
|
||||
{Ptype: "p", V0: RoleAdmin, V1: "/api/v1/comments/*", V2: "*"},
|
||||
{Ptype: "p", V0: RoleAdmin, V1: "/api/v1/groups/*", V2: "*"},
|
||||
|
||||
// 版主 - 拥有内容审核权限
|
||||
{Ptype: "p", V0: RoleModerator, V1: "/api/v1/posts/*", V2: "GET"},
|
||||
{Ptype: "p", V0: RoleModerator, V1: "/api/v1/posts/*", V2: "DELETE"},
|
||||
{Ptype: "p", V0: RoleModerator, V1: "/api/v1/comments/*", V2: "GET"},
|
||||
{Ptype: "p", V0: RoleModerator, V1: "/api/v1/comments/*", V2: "DELETE"},
|
||||
{Ptype: "p", V0: RoleModerator, V1: "/api/v1/groups/*", V2: "GET"},
|
||||
|
||||
// 普通用户 - 拥有基本权限
|
||||
{Ptype: "p", V0: RoleUser, V1: "/api/v1/posts", V2: "GET"},
|
||||
{Ptype: "p", V0: RoleUser, V1: "/api/v1/posts/*", V2: "GET"},
|
||||
{Ptype: "p", V0: RoleUser, V1: "/api/v1/posts", V2: "POST"},
|
||||
{Ptype: "p", V0: RoleUser, V1: "/api/v1/comments/*", V2: "GET"},
|
||||
{Ptype: "p", V0: RoleUser, V1: "/api/v1/comments", V2: "POST"},
|
||||
{Ptype: "p", V0: RoleUser, V1: "/api/v1/users/me", V2: "GET"},
|
||||
{Ptype: "p", V0: RoleUser, V1: "/api/v1/users/me", V2: "PUT"},
|
||||
{Ptype: "p", V0: RoleUser, V1: "/api/v1/groups", V2: "GET"},
|
||||
{Ptype: "p", V0: RoleUser, V1: "/api/v1/groups/*", V2: "GET"},
|
||||
|
||||
// 被封禁用户 - 无权限
|
||||
}
|
||||
|
||||
// 批量插入权限策略
|
||||
if err := db.Create(&permissions).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Seeded %d permissions successfully", len(permissions))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -46,9 +46,10 @@ type Post struct {
|
||||
HotScore float64 `json:"hot_score" gorm:"column:hot_score;default:0;index:idx_posts_hot_score_created,priority:1"`
|
||||
|
||||
// 置顶/锁定
|
||||
IsPinned bool `json:"is_pinned" gorm:"default:false"`
|
||||
IsLocked bool `json:"is_locked" gorm:"default:false"`
|
||||
IsDeleted bool `json:"-" gorm:"default:false"`
|
||||
IsPinned bool `json:"is_pinned" gorm:"default:false"`
|
||||
IsFeatured bool `json:"is_featured" gorm:"default:false"` // 加精
|
||||
IsLocked bool `json:"is_locked" gorm:"default:false"`
|
||||
IsDeleted bool `json:"-" gorm:"default:false"`
|
||||
|
||||
// 投票
|
||||
IsVote bool `json:"is_vote" gorm:"column:is_vote;default:false"`
|
||||
|
||||
@@ -298,3 +298,96 @@ func (r *CommentRepository) IsLiked(commentID, userID string) bool {
|
||||
r.db.Model(&model.CommentLike{}).Where("comment_id = ? AND user_id = ?", commentID, userID).Count(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
// AdminCommentListFilter 管理端评论列表筛选条件
|
||||
type AdminCommentListFilter struct {
|
||||
Keyword string
|
||||
PostID string
|
||||
AuthorID string
|
||||
Status string
|
||||
StartDate string
|
||||
EndDate string
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// GetAdminCommentList 获取管理端评论列表
|
||||
func (r *CommentRepository) GetAdminCommentList(filter AdminCommentListFilter) ([]*model.Comment, int64, error) {
|
||||
var comments []*model.Comment
|
||||
var total int64
|
||||
|
||||
query := r.db.Model(&model.Comment{}).Preload("User")
|
||||
|
||||
// 应用筛选条件
|
||||
if filter.Keyword != "" {
|
||||
query = query.Where("content LIKE ?", "%"+filter.Keyword+"%")
|
||||
}
|
||||
if filter.PostID != "" {
|
||||
query = query.Where("post_id = ?", filter.PostID)
|
||||
}
|
||||
if filter.AuthorID != "" {
|
||||
query = query.Where("user_id = ?", filter.AuthorID)
|
||||
}
|
||||
if filter.Status != "" {
|
||||
query = query.Where("status = ?", filter.Status)
|
||||
}
|
||||
if filter.StartDate != "" {
|
||||
query = query.Where("created_at >= ?", filter.StartDate)
|
||||
}
|
||||
if filter.EndDate != "" {
|
||||
query = query.Where("created_at <= ?", filter.EndDate+" 23:59:59")
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (filter.Page - 1) * filter.PageSize
|
||||
if err := query.Offset(offset).Limit(filter.PageSize).Order("created_at DESC").Find(&comments).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return comments, total, nil
|
||||
}
|
||||
|
||||
// GetAdminCommentByID 获取管理端评论详情(包含关联信息)
|
||||
func (r *CommentRepository) GetAdminCommentByID(id string) (*model.Comment, error) {
|
||||
var comment model.Comment
|
||||
err := r.db.Preload("User").First(&comment, "id = ?", id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &comment, nil
|
||||
}
|
||||
|
||||
// BatchDelete 批量删除评论
|
||||
func (r *CommentRepository) BatchDelete(ids []string) ([]string, error) {
|
||||
var failedIDs []string
|
||||
|
||||
for _, id := range ids {
|
||||
if err := r.Delete(id); err != nil {
|
||||
failedIDs = append(failedIDs, id)
|
||||
}
|
||||
}
|
||||
|
||||
return failedIDs, nil
|
||||
}
|
||||
|
||||
// GetCommentsByPostIDForAdmin 管理端获取帖子的评论列表(包含所有状态)
|
||||
func (r *CommentRepository) GetCommentsByPostIDForAdmin(postID string, page, pageSize int) ([]*model.Comment, int64, error) {
|
||||
var comments []*model.Comment
|
||||
var total int64
|
||||
|
||||
r.db.Model(&model.Comment{}).Where("post_id = ?", postID).Count(&total)
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
err := r.db.Where("post_id = ?", postID).
|
||||
Preload("User").
|
||||
Offset(offset).Limit(pageSize).
|
||||
Order("created_at DESC").
|
||||
Find(&comments).Error
|
||||
|
||||
return comments, total, err
|
||||
}
|
||||
|
||||
@@ -35,6 +35,20 @@ type GroupRepository interface {
|
||||
GetAnnouncements(groupID string, page, pageSize int) ([]model.GroupAnnouncement, int64, error)
|
||||
GetAnnouncementByID(id string) (*model.GroupAnnouncement, error)
|
||||
DeleteAnnouncement(id string) error
|
||||
|
||||
// 管理端群组操作
|
||||
GetGroupList(page, pageSize int, keyword, status, ownerID, startDate, endDate string) ([]model.Group, int64, error)
|
||||
GetGroupWithOwner(groupID string) (*model.Group, error)
|
||||
GetLatestAnnouncement(groupID string) (*model.GroupAnnouncement, error)
|
||||
UpdateGroupStatus(groupID string, status string) error
|
||||
GetGroupPostCount(groupID string) (int64, error)
|
||||
GetMembersWithUserInfo(groupID string, page, pageSize int) ([]GroupMemberWithUser, int64, error)
|
||||
}
|
||||
|
||||
// GroupMemberWithUser 群成员带用户信息的结构
|
||||
type GroupMemberWithUser struct {
|
||||
model.GroupMember
|
||||
User *model.User
|
||||
}
|
||||
|
||||
// groupRepository 群组仓库实现
|
||||
@@ -240,3 +254,159 @@ func (r *groupRepository) GetAnnouncementByID(id string) (*model.GroupAnnounceme
|
||||
func (r *groupRepository) DeleteAnnouncement(id string) error {
|
||||
return r.db.Delete(&model.GroupAnnouncement{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// ==================== 管理端群组操作 ====================
|
||||
|
||||
// GetGroupList 获取群组列表(管理端)
|
||||
func (r *groupRepository) GetGroupList(page, pageSize int, keyword, status, ownerID, startDate, endDate string) ([]model.Group, int64, error) {
|
||||
var groups []model.Group
|
||||
var total int64
|
||||
|
||||
query := r.db.Model(&model.Group{})
|
||||
|
||||
// 关键词搜索
|
||||
if keyword != "" {
|
||||
query = query.Where("name LIKE ? OR description LIKE ?", "%"+keyword+"%", "%"+keyword+"%")
|
||||
}
|
||||
|
||||
// 状态筛选(这里假设群组有status字段,如果没有则忽略)
|
||||
// 注意:当前Group模型没有status字段,状态通过是否解散来判断
|
||||
// 如果需要status字段,需要在模型中添加
|
||||
|
||||
// 群主筛选
|
||||
if ownerID != "" {
|
||||
query = query.Where("owner_id = ?", ownerID)
|
||||
}
|
||||
|
||||
// 日期筛选
|
||||
if startDate != "" {
|
||||
query = query.Where("created_at >= ?", startDate+" 00:00:00")
|
||||
}
|
||||
if endDate != "" {
|
||||
query = query.Where("created_at <= ?", endDate+" 23:59:59")
|
||||
}
|
||||
|
||||
query.Count(&total)
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&groups).Error
|
||||
return groups, total, err
|
||||
}
|
||||
|
||||
// GetGroupWithOwner 获取群组详情(包含群主信息)
|
||||
func (r *groupRepository) GetGroupWithOwner(groupID string) (*model.Group, error) {
|
||||
var group model.Group
|
||||
err := r.db.First(&group, "id = ?", groupID).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &group, nil
|
||||
}
|
||||
|
||||
// GetLatestAnnouncement 获取群组最新公告
|
||||
func (r *groupRepository) GetLatestAnnouncement(groupID string) (*model.GroupAnnouncement, error) {
|
||||
var announcement model.GroupAnnouncement
|
||||
err := r.db.Where("group_id = ?", groupID).
|
||||
Order("is_pinned DESC, created_at DESC").
|
||||
First(&announcement).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &announcement, nil
|
||||
}
|
||||
|
||||
// UpdateGroupStatus 更新群组状态
|
||||
func (r *groupRepository) UpdateGroupStatus(groupID string, status string) error {
|
||||
// 注意:当前Group模型没有status字段
|
||||
// 这里我们通过更新一个虚拟字段或者使用其他方式来实现
|
||||
// 暂时通过更新updated_at来模拟状态更新
|
||||
// 如果需要真正的状态字段,需要在模型中添加
|
||||
return r.db.Model(&model.Group{}).Where("id = ?", groupID).
|
||||
Update("updated_at", gorm.Expr("NOW()")).Error
|
||||
}
|
||||
|
||||
// GetGroupPostCount 获取群组帖子数量
|
||||
func (r *groupRepository) GetGroupPostCount(groupID string) (int64, error) {
|
||||
var count int64
|
||||
// 假设帖子表有group_id字段
|
||||
err := r.db.Model(&model.Post{}).Where("group_id = ?", groupID).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetMembersWithUserInfo 获取群成员列表(包含用户信息)
|
||||
func (r *groupRepository) GetMembersWithUserInfo(groupID string, page, pageSize int) ([]GroupMemberWithUser, int64, error) {
|
||||
var members []GroupMemberWithUser
|
||||
var total int64
|
||||
|
||||
// 先获取总数
|
||||
err := r.db.Model(&model.GroupMember{}).Where("group_id = ?", groupID).Count(&total).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 联表查询获取成员和用户信息
|
||||
offset := (page - 1) * pageSize
|
||||
query := `
|
||||
SELECT gm.*, u.id as user_id, u.username, u.nickname, u.avatar
|
||||
FROM group_members gm
|
||||
LEFT JOIN users u ON gm.user_id = u.id
|
||||
WHERE gm.group_id = ?
|
||||
ORDER BY
|
||||
CASE gm.role
|
||||
WHEN 'owner' THEN 1
|
||||
WHEN 'admin' THEN 2
|
||||
WHEN 'member' THEN 3
|
||||
END,
|
||||
gm.join_time ASC
|
||||
LIMIT ? OFFSET ?
|
||||
`
|
||||
|
||||
type memberRow struct {
|
||||
ID string `gorm:"column:id"`
|
||||
GroupID string `gorm:"column:group_id"`
|
||||
UserID string `gorm:"column:user_id"`
|
||||
Role string `gorm:"column:role"`
|
||||
Nickname string `gorm:"column:nickname"`
|
||||
Muted bool `gorm:"column:muted"`
|
||||
JoinTime string `gorm:"column:join_time"`
|
||||
CreatedAt string `gorm:"column:created_at"`
|
||||
UpdatedAt string `gorm:"column:updated_at"`
|
||||
// 用户信息
|
||||
Username string `gorm:"column:username"`
|
||||
UserNickname string `gorm:"column:nickname"`
|
||||
UserAvatar string `gorm:"column:avatar"`
|
||||
}
|
||||
|
||||
var rows []memberRow
|
||||
err = r.db.Raw(query, groupID, pageSize, offset).Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
members = make([]GroupMemberWithUser, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
member := model.GroupMember{
|
||||
ID: row.ID,
|
||||
GroupID: row.GroupID,
|
||||
UserID: row.UserID,
|
||||
Role: row.Role,
|
||||
Nickname: row.Nickname,
|
||||
Muted: row.Muted,
|
||||
}
|
||||
user := &model.User{
|
||||
ID: row.UserID,
|
||||
Username: row.Username,
|
||||
Nickname: row.UserNickname,
|
||||
Avatar: row.UserAvatar,
|
||||
}
|
||||
members = append(members, GroupMemberWithUser{
|
||||
GroupMember: member,
|
||||
User: user,
|
||||
})
|
||||
}
|
||||
|
||||
return members, total, nil
|
||||
}
|
||||
|
||||
@@ -529,3 +529,115 @@ func (r *PostRepository) deleteWithTx(tx *gorm.DB, id string) error {
|
||||
// 最后删除帖子本身(软删除)
|
||||
return tx.Delete(&model.Post{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// ========== Admin methods ==========
|
||||
|
||||
// AdminPostListQuery 管理端帖子列表查询参数
|
||||
type AdminPostListQuery struct {
|
||||
Page int
|
||||
PageSize int
|
||||
Keyword string
|
||||
Status string // 为空表示所有状态
|
||||
AuthorID string
|
||||
StartDate string // 格式: 2006-01-02
|
||||
EndDate string // 格式: 2006-01-02
|
||||
}
|
||||
|
||||
// AdminList 管理端分页获取帖子列表(支持多条件筛选)
|
||||
func (r *PostRepository) AdminList(query AdminPostListQuery) ([]*model.Post, int64, error) {
|
||||
var posts []*model.Post
|
||||
var total int64
|
||||
|
||||
db := r.db.Model(&model.Post{})
|
||||
|
||||
// 关键词搜索(标题或内容)
|
||||
if query.Keyword != "" {
|
||||
if r.db.Dialector.Name() == "postgres" {
|
||||
db = db.Where(
|
||||
"to_tsvector('simple', COALESCE(title, '') || ' ' || COALESCE(content, '')) @@ plainto_tsquery('simple', ?)",
|
||||
query.Keyword,
|
||||
)
|
||||
} else {
|
||||
searchPattern := "%" + query.Keyword + "%"
|
||||
db = db.Where("title LIKE ? OR content LIKE ?", searchPattern, searchPattern)
|
||||
}
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if query.Status != "" {
|
||||
db = db.Where("status = ?", query.Status)
|
||||
}
|
||||
|
||||
// 作者筛选
|
||||
if query.AuthorID != "" {
|
||||
db = db.Where("user_id = ?", query.AuthorID)
|
||||
}
|
||||
|
||||
// 日期范围筛选
|
||||
if query.StartDate != "" {
|
||||
db = db.Where("created_at >= ?", query.StartDate+" 00:00:00")
|
||||
}
|
||||
if query.EndDate != "" {
|
||||
db = db.Where("created_at <= ?", query.EndDate+" 23:59:59")
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
db.Count(&total)
|
||||
|
||||
// 分页查询
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
err := db.Preload("User").Preload("Images").
|
||||
Offset(offset).Limit(query.PageSize).
|
||||
Order("created_at DESC").
|
||||
Find(&posts).Error
|
||||
|
||||
return posts, total, err
|
||||
}
|
||||
|
||||
// GetByIDForAdmin 管理端根据ID获取帖子(包含所有状态)
|
||||
func (r *PostRepository) GetByIDForAdmin(id string) (*model.Post, error) {
|
||||
var post model.Post
|
||||
err := r.db.Preload("User").Preload("Images").First(&post, "id = ?", id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
// BatchDelete 批量删除帖子
|
||||
func (r *PostRepository) BatchDelete(ids []string) ([]string, error) {
|
||||
var failedIDs []string
|
||||
|
||||
for _, id := range ids {
|
||||
if err := r.Delete(id); err != nil {
|
||||
failedIDs = append(failedIDs, id)
|
||||
}
|
||||
}
|
||||
|
||||
return failedIDs, nil
|
||||
}
|
||||
|
||||
// BatchUpdateStatus 批量更新帖子审核状态
|
||||
func (r *PostRepository) BatchUpdateStatus(ids []string, status model.PostStatus, reviewedBy string) ([]string, error) {
|
||||
var failedIDs []string
|
||||
|
||||
for _, id := range ids {
|
||||
if err := r.UpdateModerationStatus(id, status, "", reviewedBy); err != nil {
|
||||
failedIDs = append(failedIDs, id)
|
||||
}
|
||||
}
|
||||
|
||||
return failedIDs, nil
|
||||
}
|
||||
|
||||
// UpdatePinStatus 更新帖子置顶状态
|
||||
func (r *PostRepository) UpdatePinStatus(postID string, isPinned bool) error {
|
||||
return r.db.Model(&model.Post{}).Where("id = ?", postID).
|
||||
Update("is_pinned", isPinned).Error
|
||||
}
|
||||
|
||||
// UpdateFeatureStatus 更新帖子加精状态
|
||||
func (r *PostRepository) UpdateFeatureStatus(postID string, isFeatured bool) error {
|
||||
return r.db.Model(&model.Post{}).Where("id = ?", postID).
|
||||
Update("is_featured", isFeatured).Error
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ type RoleRepository interface {
|
||||
|
||||
// GetAllRoles 获取所有角色
|
||||
GetAllRoles(ctx context.Context) ([]model.Role, error)
|
||||
|
||||
// GetRoleUserCount 获取角色对应的用户数量
|
||||
GetRoleUserCount(ctx context.Context, role string) (int64, error)
|
||||
}
|
||||
|
||||
type roleRepository struct {
|
||||
@@ -95,3 +98,12 @@ func (r *roleRepository) GetAllRoles(ctx context.Context) ([]model.Role, error)
|
||||
err := r.db.WithContext(ctx).Order("priority DESC").Find(&roles).Error
|
||||
return roles, err
|
||||
}
|
||||
|
||||
func (r *roleRepository) GetRoleUserCount(ctx context.Context, role string) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).
|
||||
Model(&model.UserRole{}).
|
||||
Where("role = ?", role).
|
||||
Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
@@ -403,3 +403,53 @@ func (r *UserRepository) GetMutualFollowStatus(currentUserID string, targetUserI
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// AdminList 管理端分页获取用户列表(支持关键词和状态筛选)
|
||||
func (r *UserRepository) AdminList(page, pageSize int, keyword, status string) ([]*model.User, int64, error) {
|
||||
var users []*model.User
|
||||
var total int64
|
||||
|
||||
query := r.db.Model(&model.User{})
|
||||
|
||||
// 关键词搜索(用户名、昵称、邮箱)
|
||||
if keyword != "" {
|
||||
if r.db.Dialector.Name() == "postgres" {
|
||||
query = query.Where(
|
||||
"username ILIKE ? OR nickname ILIKE ? OR email ILIKE ?",
|
||||
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%",
|
||||
)
|
||||
} else {
|
||||
lowerSearchPattern := "%" + strings.ToLower(keyword) + "%"
|
||||
query = query.Where(
|
||||
"LOWER(username) LIKE ? OR LOWER(nickname) LIKE ? OR LOWER(email) LIKE ?",
|
||||
lowerSearchPattern, lowerSearchPattern, lowerSearchPattern,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if status != "" {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
query.Count(&total)
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
err := query.Order("created_at DESC, id DESC").Offset(offset).Limit(pageSize).Find(&users).Error
|
||||
|
||||
return users, total, err
|
||||
}
|
||||
|
||||
// GetCommentsCount 获取用户评论数
|
||||
func (r *UserRepository) GetCommentsCount(userID string) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.Model(&model.Comment{}).
|
||||
Where("user_id = ?", userID).
|
||||
Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// UpdateStatus 更新用户状态
|
||||
func (r *UserRepository) UpdateStatus(userID string, status model.UserStatus) error {
|
||||
return r.db.Model(&model.User{}).Where("id = ?", userID).Update("status", status).Error
|
||||
}
|
||||
|
||||
@@ -11,23 +11,28 @@ import (
|
||||
|
||||
// Router 路由配置
|
||||
type Router struct {
|
||||
engine *gin.Engine
|
||||
userHandler *handler.UserHandler
|
||||
postHandler *handler.PostHandler
|
||||
commentHandler *handler.CommentHandler
|
||||
messageHandler *handler.MessageHandler
|
||||
notificationHandler *handler.NotificationHandler
|
||||
uploadHandler *handler.UploadHandler
|
||||
pushHandler *handler.PushHandler
|
||||
systemMessageHandler *handler.SystemMessageHandler
|
||||
groupHandler *handler.GroupHandler
|
||||
stickerHandler *handler.StickerHandler
|
||||
gorseHandler *handler.GorseHandler
|
||||
voteHandler *handler.VoteHandler
|
||||
scheduleHandler *handler.ScheduleHandler
|
||||
roleHandler *handler.RoleHandler
|
||||
jwtService *service.JWTService
|
||||
casbinService service.CasbinService
|
||||
engine *gin.Engine
|
||||
userHandler *handler.UserHandler
|
||||
postHandler *handler.PostHandler
|
||||
commentHandler *handler.CommentHandler
|
||||
messageHandler *handler.MessageHandler
|
||||
notificationHandler *handler.NotificationHandler
|
||||
uploadHandler *handler.UploadHandler
|
||||
pushHandler *handler.PushHandler
|
||||
systemMessageHandler *handler.SystemMessageHandler
|
||||
groupHandler *handler.GroupHandler
|
||||
stickerHandler *handler.StickerHandler
|
||||
gorseHandler *handler.GorseHandler
|
||||
voteHandler *handler.VoteHandler
|
||||
scheduleHandler *handler.ScheduleHandler
|
||||
roleHandler *handler.RoleHandler
|
||||
adminUserHandler *handler.AdminUserHandler
|
||||
adminPostHandler *handler.AdminPostHandler
|
||||
adminCommentHandler *handler.AdminCommentHandler
|
||||
adminGroupHandler *handler.AdminGroupHandler
|
||||
adminDashboardHandler *handler.AdminDashboardHandler
|
||||
jwtService *service.JWTService
|
||||
casbinService service.CasbinService
|
||||
}
|
||||
|
||||
// New 创建路由
|
||||
@@ -47,6 +52,11 @@ func New(
|
||||
voteHandler *handler.VoteHandler,
|
||||
scheduleHandler *handler.ScheduleHandler,
|
||||
roleHandler *handler.RoleHandler,
|
||||
adminUserHandler *handler.AdminUserHandler,
|
||||
adminPostHandler *handler.AdminPostHandler,
|
||||
adminCommentHandler *handler.AdminCommentHandler,
|
||||
adminGroupHandler *handler.AdminGroupHandler,
|
||||
adminDashboardHandler *handler.AdminDashboardHandler,
|
||||
activityService service.UserActivityService,
|
||||
casbinService service.CasbinService,
|
||||
) *Router {
|
||||
@@ -56,23 +66,28 @@ func New(
|
||||
userHandler.SetActivityService(activityService)
|
||||
|
||||
r := &Router{
|
||||
engine: gin.Default(),
|
||||
userHandler: userHandler,
|
||||
postHandler: postHandler,
|
||||
commentHandler: commentHandler,
|
||||
messageHandler: messageHandler,
|
||||
notificationHandler: notificationHandler,
|
||||
uploadHandler: uploadHandler,
|
||||
pushHandler: pushHandler,
|
||||
systemMessageHandler: systemMessageHandler,
|
||||
groupHandler: groupHandler,
|
||||
stickerHandler: stickerHandler,
|
||||
gorseHandler: gorseHandler,
|
||||
voteHandler: voteHandler,
|
||||
scheduleHandler: scheduleHandler,
|
||||
roleHandler: roleHandler,
|
||||
jwtService: jwtService,
|
||||
casbinService: casbinService,
|
||||
engine: gin.Default(),
|
||||
userHandler: userHandler,
|
||||
postHandler: postHandler,
|
||||
commentHandler: commentHandler,
|
||||
messageHandler: messageHandler,
|
||||
notificationHandler: notificationHandler,
|
||||
uploadHandler: uploadHandler,
|
||||
pushHandler: pushHandler,
|
||||
systemMessageHandler: systemMessageHandler,
|
||||
groupHandler: groupHandler,
|
||||
stickerHandler: stickerHandler,
|
||||
gorseHandler: gorseHandler,
|
||||
voteHandler: voteHandler,
|
||||
scheduleHandler: scheduleHandler,
|
||||
roleHandler: roleHandler,
|
||||
adminUserHandler: adminUserHandler,
|
||||
adminPostHandler: adminPostHandler,
|
||||
adminCommentHandler: adminCommentHandler,
|
||||
adminGroupHandler: adminGroupHandler,
|
||||
adminDashboardHandler: adminDashboardHandler,
|
||||
jwtService: jwtService,
|
||||
casbinService: casbinService,
|
||||
}
|
||||
|
||||
r.setupRoutes()
|
||||
@@ -365,9 +380,61 @@ func (r *Router) setupRoutes() {
|
||||
{
|
||||
// 角色管理
|
||||
admin.GET("/roles", r.roleHandler.GetAllRoles)
|
||||
admin.GET("/roles/:name", r.roleHandler.GetRoleDetail)
|
||||
admin.GET("/roles/:name/permissions", r.roleHandler.GetRolePermissions)
|
||||
admin.PUT("/roles/:name/permissions", r.roleHandler.UpdateRolePermissions)
|
||||
admin.GET("/permissions", r.roleHandler.GetAllPermissions)
|
||||
admin.GET("/users/:id/roles", r.roleHandler.GetUserRoles)
|
||||
admin.POST("/users/:id/roles", r.roleHandler.AssignRole)
|
||||
admin.DELETE("/users/:id/roles/:role", r.roleHandler.RemoveRole)
|
||||
|
||||
// 用户管理
|
||||
if r.adminUserHandler != nil {
|
||||
admin.GET("/users", r.adminUserHandler.GetUserList)
|
||||
admin.GET("/users/:id", r.adminUserHandler.GetUserDetail)
|
||||
admin.PUT("/users/:id/status", r.adminUserHandler.UpdateUserStatus)
|
||||
}
|
||||
|
||||
// 帖子管理
|
||||
if r.adminPostHandler != nil {
|
||||
admin.GET("/posts", r.adminPostHandler.GetPostList)
|
||||
admin.GET("/posts/:id", r.adminPostHandler.GetPostDetail)
|
||||
admin.DELETE("/posts/:id", r.adminPostHandler.DeletePost)
|
||||
admin.PUT("/posts/:id/moderate", r.adminPostHandler.ModeratePost)
|
||||
admin.POST("/posts/batch-delete", r.adminPostHandler.BatchDeletePosts)
|
||||
admin.POST("/posts/batch-moderate", r.adminPostHandler.BatchModeratePosts)
|
||||
admin.PUT("/posts/:id/pin", r.adminPostHandler.SetPostPin)
|
||||
admin.PUT("/posts/:id/feature", r.adminPostHandler.SetPostFeature)
|
||||
}
|
||||
|
||||
// 评论管理
|
||||
if r.adminCommentHandler != nil {
|
||||
admin.GET("/comments", r.adminCommentHandler.GetCommentList)
|
||||
admin.GET("/comments/:id", r.adminCommentHandler.GetCommentDetail)
|
||||
admin.DELETE("/comments/:id", r.adminCommentHandler.DeleteComment)
|
||||
admin.POST("/comments/batch-delete", r.adminCommentHandler.BatchDeleteComments)
|
||||
admin.GET("/comments/post/:postId", r.adminCommentHandler.GetPostComments)
|
||||
}
|
||||
|
||||
// 群组管理
|
||||
if r.adminGroupHandler != nil {
|
||||
admin.GET("/groups", r.adminGroupHandler.GetGroupList)
|
||||
admin.GET("/groups/:id", r.adminGroupHandler.GetGroupDetail)
|
||||
admin.GET("/groups/:id/members", r.adminGroupHandler.GetGroupMembers)
|
||||
admin.DELETE("/groups/:id", r.adminGroupHandler.DeleteGroup)
|
||||
admin.PUT("/groups/:id/status", r.adminGroupHandler.UpdateGroupStatus)
|
||||
admin.DELETE("/groups/:id/members/:userId", r.adminGroupHandler.RemoveMember)
|
||||
admin.PUT("/groups/:id/transfer", r.adminGroupHandler.TransferOwner)
|
||||
}
|
||||
|
||||
// 仪表盘
|
||||
if r.adminDashboardHandler != nil {
|
||||
admin.GET("/dashboard/stats", r.adminDashboardHandler.GetStats)
|
||||
admin.GET("/dashboard/user-activity", r.adminDashboardHandler.GetUserActivityTrend)
|
||||
admin.GET("/dashboard/content-stats", r.adminDashboardHandler.GetContentStats)
|
||||
admin.GET("/dashboard/pending-content", r.adminDashboardHandler.GetPendingContent)
|
||||
admin.GET("/dashboard/online-users", r.adminDashboardHandler.GetOnlineUsers)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
"carrot_bbs/internal/repository"
|
||||
"carrot_bbs/internal/service"
|
||||
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v3"
|
||||
gormadapter "github.com/casbin/gorm-adapter/v3"
|
||||
"github.com/google/wire"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -22,19 +23,24 @@ var CasbinSet = wire.NewSet(
|
||||
|
||||
// ProvideCasbinEnforcer 提供 Casbin Enforcer
|
||||
func ProvideCasbinEnforcer(cfg *config.Config, db *gorm.DB) *casbin.Enforcer {
|
||||
// 创建 Casbin Enforcer
|
||||
// 注意:这里使用文件适配器作为示例,实际使用时需要替换为数据库适配器
|
||||
enforcer, err := casbin.NewEnforcer(cfg.Casbin.ModelPath)
|
||||
// 创建 GORM 适配器,连接数据库
|
||||
adapter, err := gormadapter.NewAdapterByDB(db)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create Casbin GORM adapter: %v", err)
|
||||
}
|
||||
|
||||
// 创建 Casbin Enforcer,使用数据库适配器
|
||||
enforcer, err := casbin.NewEnforcer(cfg.Casbin.ModelPath, adapter)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create Casbin enforcer: %v", err)
|
||||
}
|
||||
|
||||
// 加载策略
|
||||
// 从数据库加载策略
|
||||
if err := enforcer.LoadPolicy(); err != nil {
|
||||
log.Printf("[WARNING] Failed to load Casbin policy: %v", err)
|
||||
}
|
||||
|
||||
log.Println("[Wire] Initialized Casbin enforcer")
|
||||
log.Println("[Wire] Initialized Casbin enforcer with GORM adapter")
|
||||
return enforcer
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@ var HandlerSet = wire.NewSet(
|
||||
handler.NewStickerHandler,
|
||||
handler.NewVoteHandler,
|
||||
handler.NewRoleHandler,
|
||||
handler.NewAdminUserHandler,
|
||||
handler.NewAdminPostHandler,
|
||||
handler.NewAdminCommentHandler,
|
||||
handler.NewAdminGroupHandler,
|
||||
handler.NewAdminDashboardHandler,
|
||||
|
||||
// 需要特殊处理的 Handler
|
||||
ProvideUserHandler,
|
||||
|
||||
@@ -43,6 +43,11 @@ var ServiceSet = wire.NewSet(
|
||||
ProvideUploadService,
|
||||
ProvideUserActivityService,
|
||||
ProvideRoleService,
|
||||
ProvideAdminUserService,
|
||||
ProvideAdminPostService,
|
||||
ProvideAdminCommentService,
|
||||
ProvideAdminGroupService,
|
||||
ProvideAdminDashboardService,
|
||||
)
|
||||
|
||||
// ProvideJWTService 提供 JWT 服务
|
||||
@@ -214,3 +219,38 @@ func ProvideRoleService(
|
||||
) service.RoleService {
|
||||
return service.NewRoleService(roleRepo, casbinSvc)
|
||||
}
|
||||
|
||||
// ProvideAdminUserService 提供管理端用户服务
|
||||
func ProvideAdminUserService(userRepo *repository.UserRepository) service.AdminUserService {
|
||||
return service.NewAdminUserService(userRepo)
|
||||
}
|
||||
|
||||
// ProvideAdminPostService 提供管理端帖子服务
|
||||
func ProvideAdminPostService(postRepo *repository.PostRepository, cacheBackend cache.Cache) service.AdminPostService {
|
||||
return service.NewAdminPostService(postRepo, cacheBackend)
|
||||
}
|
||||
|
||||
// ProvideAdminCommentService 提供管理端评论服务
|
||||
func ProvideAdminCommentService(commentRepo *repository.CommentRepository, postRepo *repository.PostRepository) service.AdminCommentService {
|
||||
return service.NewAdminCommentService(commentRepo, postRepo)
|
||||
}
|
||||
|
||||
// ProvideAdminGroupService 提供管理端群组服务
|
||||
func ProvideAdminGroupService(
|
||||
groupRepo repository.GroupRepository,
|
||||
userRepo *repository.UserRepository,
|
||||
) service.AdminGroupService {
|
||||
return service.NewAdminGroupService(groupRepo, userRepo)
|
||||
}
|
||||
|
||||
// ProvideAdminDashboardService 提供管理端仪表盘服务
|
||||
func ProvideAdminDashboardService(
|
||||
db *gorm.DB,
|
||||
userRepo *repository.UserRepository,
|
||||
postRepo *repository.PostRepository,
|
||||
commentRepo *repository.CommentRepository,
|
||||
groupRepo repository.GroupRepository,
|
||||
activityRepo *repository.UserActivityRepository,
|
||||
) service.AdminDashboardService {
|
||||
return service.NewAdminDashboardService(db, userRepo, postRepo, commentRepo, groupRepo, activityRepo)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user