feat: 管理首页帖子总数统计(总数/已发布/待审核)
- PostRepo 新增 CountPostsByStatus 和 CountPending 方法 - Service 层定义 postAdminStatStore 接口 (ISP) - AdminService 注入 postRepo,新增 PostStats 结构体和 GetPostStats 方法 - AdminController.Dashboard 调用 GetPostStats 并传参到模板 - Dashboard 帖子卡片显示总数 + 已发布 + 待审核(橙色徽章) - DI 层将 AdminController 创建从 buildDepsCore 移至 buildDeps,以便注入 postRepo
This commit is contained in:
@ -29,10 +29,17 @@ func (ac *AdminController) Dashboard(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
totalUsers = 0
|
totalUsers = 0
|
||||||
}
|
}
|
||||||
|
postStats, err := ac.adminService.GetPostStats()
|
||||||
|
if err != nil {
|
||||||
|
postStats = &service.PostStats{}
|
||||||
|
}
|
||||||
storeMetrics := ac.adminService.GetStoreMetrics()
|
storeMetrics := ac.adminService.GetStoreMetrics()
|
||||||
c.HTML(http.StatusOK, "admin/dashboard/index.html", common.BuildAdminPageData(c, gin.H{
|
c.HTML(http.StatusOK, "admin/dashboard/index.html", common.BuildAdminPageData(c, gin.H{
|
||||||
"Title": "管理首页",
|
"Title": "管理首页",
|
||||||
"TotalUsers": totalUsers,
|
"TotalUsers": totalUsers,
|
||||||
|
"PostTotal": postStats.Total,
|
||||||
|
"PostApproved": postStats.Approved,
|
||||||
|
"PostPending": postStats.Pending,
|
||||||
"StoreType": storeMetrics.Type,
|
"StoreType": storeMetrics.Type,
|
||||||
"StoreHealthy": storeMetrics.Healthy,
|
"StoreHealthy": storeMetrics.Healthy,
|
||||||
"StoreDegraded": storeMetrics.Degraded,
|
"StoreDegraded": storeMetrics.Degraded,
|
||||||
|
|||||||
@ -6,12 +6,13 @@ import (
|
|||||||
"metazone.cc/metalab/internal/session"
|
"metazone.cc/metalab/internal/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// adminUseCase AdminController 对 AdminService 的最小依赖(ISP:5 个方法)
|
// adminUseCase AdminController 对 AdminService 的最小依赖(ISP:6 个方法)
|
||||||
type adminUseCase interface {
|
type adminUseCase interface {
|
||||||
ListUsers(params service.ListUsersParams) (*service.ListUsersResult, error)
|
ListUsers(params service.ListUsersParams) (*service.ListUsersResult, error)
|
||||||
UpdateUserStatus(operatorUID, targetUID uint, newStatus string) error
|
UpdateUserStatus(operatorUID, targetUID uint, newStatus string) error
|
||||||
ResetUserToken(operatorUID, targetUID uint) error
|
ResetUserToken(operatorUID, targetUID uint) error
|
||||||
CountUsers() (int64, error)
|
CountUsers() (int64, error)
|
||||||
|
GetPostStats() (*service.PostStats, error)
|
||||||
GetStoreMetrics() session.StoreMetrics
|
GetStoreMetrics() session.StoreMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -137,6 +137,24 @@ func (r *PostRepo) SoftDelete(id uint) error {
|
|||||||
return r.db.Delete(&model.Post{}, id).Error
|
return r.db.Delete(&model.Post{}, id).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CountPostsByStatus 统计帖子数量(status 为空则统计全部未删除帖子)
|
||||||
|
func (r *PostRepo) CountPostsByStatus(status string) (int64, error) {
|
||||||
|
query := r.buildQuery("", status)
|
||||||
|
var total int64
|
||||||
|
err := query.Count(&total).Error
|
||||||
|
return total, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountPending 统计待审核帖子数(pending 状态 + approved 但有待审修订)
|
||||||
|
func (r *PostRepo) CountPending() (int64, error) {
|
||||||
|
var total int64
|
||||||
|
err := r.db.Table("posts").
|
||||||
|
Where("deleted_at IS NULL").
|
||||||
|
Where("status = ? OR (status = ? AND pending_body != '')", model.PostStatusPending, model.PostStatusApproved).
|
||||||
|
Count(&total).Error
|
||||||
|
return total, err
|
||||||
|
}
|
||||||
|
|
||||||
// Restore 恢复软删除
|
// Restore 恢复软删除
|
||||||
func (r *PostRepo) Restore(id uint) error {
|
func (r *PostRepo) Restore(id uint) error {
|
||||||
result := r.db.Unscoped().Model(&model.Post{}).Where("id = ?", id).Update("deleted_at", nil)
|
result := r.db.Unscoped().Model(&model.Post{}).Where("id = ?", id).Update("deleted_at", nil)
|
||||||
|
|||||||
@ -34,7 +34,7 @@ type dependencies struct {
|
|||||||
func buildDepsCore(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSettings, redisClient *redis.Client) (
|
func buildDepsCore(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSettings, redisClient *redis.Client) (
|
||||||
*repository.UserRepo, *session.Manager, *service.AuthService,
|
*repository.UserRepo, *session.Manager, *service.AuthService,
|
||||||
*middleware.RateLimiter, *controller.AuthController,
|
*middleware.RateLimiter, *controller.AuthController,
|
||||||
*service.AvatarService, *middleware.AuthMiddleware, *adminCtrl.AdminController,
|
*service.AvatarService, *middleware.AuthMiddleware,
|
||||||
) {
|
) {
|
||||||
userRepo := repository.NewUserRepo(db)
|
userRepo := repository.NewUserRepo(db)
|
||||||
|
|
||||||
@ -65,6 +65,5 @@ func buildDepsCore(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSet
|
|||||||
authCtrl := controller.NewAuthController(authService, sessionMgr, rateLimiter, cfg)
|
authCtrl := controller.NewAuthController(authService, sessionMgr, rateLimiter, cfg)
|
||||||
avatarSvc := service.NewAvatarService(userRepo)
|
avatarSvc := service.NewAvatarService(userRepo)
|
||||||
authMdw := middleware.NewAuthMiddleware(cfg, sessionMgr)
|
authMdw := middleware.NewAuthMiddleware(cfg, sessionMgr)
|
||||||
adminController := adminCtrl.NewAdminController(service.NewAdminService(userRepo, sessionMgr))
|
return userRepo, sessionMgr, authService, rateLimiter, authCtrl, avatarSvc, authMdw
|
||||||
return userRepo, sessionMgr, authService, rateLimiter, authCtrl, avatarSvc, authMdw, adminController
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSettings, redisClient *redis.Client) *dependencies {
|
func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSettings, redisClient *redis.Client) *dependencies {
|
||||||
userRepo, sessionMgr, authService, _, authCtrl, avatarSvc, authMdw, adminController := buildDepsCore(db, cfg, siteSettings, redisClient)
|
userRepo, sessionMgr, authService, _, authCtrl, avatarSvc, authMdw := buildDepsCore(db, cfg, siteSettings, redisClient)
|
||||||
|
|
||||||
auditRepo := repository.NewAuditRepo(db)
|
auditRepo := repository.NewAuditRepo(db)
|
||||||
notificationRepo := repository.NewNotificationRepo(db)
|
notificationRepo := repository.NewNotificationRepo(db)
|
||||||
@ -37,6 +37,7 @@ func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSetting
|
|||||||
shortcodeSvc := service.NewShortcodeService()
|
shortcodeSvc := service.NewShortcodeService()
|
||||||
postCtrl := controller.NewPostController(postService, shortcodeSvc)
|
postCtrl := controller.NewPostController(postService, shortcodeSvc)
|
||||||
adminPostCtrl := adminCtrl.NewAdminPostController(postService)
|
adminPostCtrl := adminCtrl.NewAdminPostController(postService)
|
||||||
|
adminController := adminCtrl.NewAdminController(service.NewAdminService(userRepo, postRepo, sessionMgr))
|
||||||
studioCtrl := controller.NewStudioController(postService)
|
studioCtrl := controller.NewStudioController(postService)
|
||||||
|
|
||||||
// 用户空间
|
// 用户空间
|
||||||
|
|||||||
@ -8,12 +8,20 @@ import (
|
|||||||
|
|
||||||
type AdminService struct {
|
type AdminService struct {
|
||||||
userRepo userAdminStore
|
userRepo userAdminStore
|
||||||
|
postRepo postAdminStatStore
|
||||||
sessionManager *session.Manager
|
sessionManager *session.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostStats 帖子统计结果(管理首页用)
|
||||||
|
type PostStats struct {
|
||||||
|
Total int64 // 全部未删除帖子数
|
||||||
|
Approved int64 // 已发布数
|
||||||
|
Pending int64 // 待审核数(pending + 待审修订)
|
||||||
|
}
|
||||||
|
|
||||||
// NewAdminService 构造函数
|
// NewAdminService 构造函数
|
||||||
func NewAdminService(userRepo userAdminStore, sm *session.Manager) *AdminService {
|
func NewAdminService(userRepo userAdminStore, postRepo postAdminStatStore, sm *session.Manager) *AdminService {
|
||||||
return &AdminService{userRepo: userRepo, sessionManager: sm}
|
return &AdminService{userRepo: userRepo, postRepo: postRepo, sessionManager: sm}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListUsersParams 用户列表查询参数
|
// ListUsersParams 用户列表查询参数
|
||||||
@ -112,6 +120,23 @@ func (s *AdminService) CountUsers() (int64, error) {
|
|||||||
return s.userRepo.CountSearchUsers("", "", "")
|
return s.userRepo.CountSearchUsers("", "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPostStats 获取帖子统计(管理首页用)
|
||||||
|
func (s *AdminService) GetPostStats() (*PostStats, error) {
|
||||||
|
total, err := s.postRepo.CountPostsByStatus("")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
approved, err := s.postRepo.CountPostsByStatus(model.PostStatusApproved)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pending, err := s.postRepo.CountPending()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &PostStats{Total: total, Approved: approved, Pending: pending}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetStoreMetrics 返回会话存储状态信息(管理首页用)
|
// GetStoreMetrics 返回会话存储状态信息(管理首页用)
|
||||||
func (s *AdminService) GetStoreMetrics() session.StoreMetrics {
|
func (s *AdminService) GetStoreMetrics() session.StoreMetrics {
|
||||||
return s.sessionManager.GetStoreMetrics()
|
return s.sessionManager.GetStoreMetrics()
|
||||||
|
|||||||
@ -47,6 +47,12 @@ type spacePostStore interface {
|
|||||||
FindByUserID(userID uint, offset, limit int) ([]model.Post, int64, error)
|
FindByUserID(userID uint, offset, limit int) ([]model.Post, int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// postAdminStatStore AdminService 所需的帖子统计接口(ISP:2 个方法)
|
||||||
|
type postAdminStatStore interface {
|
||||||
|
CountPostsByStatus(status string) (int64, error)
|
||||||
|
CountPending() (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
// postNotifier PostService 所需的通知服务接口
|
// postNotifier PostService 所需的通知服务接口
|
||||||
type postNotifier interface {
|
type postNotifier interface {
|
||||||
NotifyPostApproved(userID uint, postTitle string, postID uint)
|
NotifyPostApproved(userID uint, postTitle string, postID uint)
|
||||||
|
|||||||
@ -11,7 +11,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-icon stat-green"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18"/><line x1="9" y1="9" x2="15" y2="9"/><line x1="9" y1="13" x2="15" y2="13"/></svg></div>
|
<div class="stat-icon stat-green"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18"/><line x1="9" y1="9" x2="15" y2="9"/><line x1="9" y1="13" x2="15" y2="13"/></svg></div>
|
||||||
<div class="stat-info"><div class="stat-label">帖子总数</div><div class="stat-value">—</div></div>
|
<div class="stat-info">
|
||||||
|
<div class="stat-label">帖子总数</div>
|
||||||
|
<div class="stat-value">{{.PostTotal}}</div>
|
||||||
|
<div class="stat-desc">已发布 {{.PostApproved}}<span class="stat-badge badge-warn" style="margin-left: 12px;">待审核 {{.PostPending}}</span></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-icon stat-orange"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg></div>
|
<div class="stat-icon stat-orange"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user