第 5 阶段:纠纷、通知与后台-3

This commit is contained in:
yml
2026-05-22 16:59:07 +08:00
parent aee3455e9a
commit e72decebc8
11 changed files with 447 additions and 5 deletions
@@ -0,0 +1,52 @@
package admindashboard
import "time"
type DashboardDTO struct {
Metrics MetricsDTO `json:"metrics"`
Pending PendingDTO `json:"pending"`
RecentOrders []RecentOrderDTO `json:"recent_orders"`
RecentDisputes []RecentDisputeDTO `json:"recent_disputes"`
GeneratedAt time.Time `json:"generated_at"`
}
type MetricsDTO struct {
TotalUsers int64 `json:"total_users"`
VerifiedUsers int64 `json:"verified_users"`
TotalListings int64 `json:"total_listings"`
PublishedListings int64 `json:"published_listings"`
TotalOrders int64 `json:"total_orders"`
RentingOrders int64 `json:"renting_orders"`
TodayOrders int64 `json:"today_orders"`
TodayLedgerAmount float64 `json:"today_ledger_amount"`
}
type PendingDTO struct {
ListingReviews int64 `json:"listing_reviews"`
Disputes int64 `json:"disputes"`
PendingHandoffs int64 `json:"pending_handoffs"`
PendingReturnConfirms int64 `json:"pending_return_confirms"`
}
type RecentOrderDTO struct {
ID uint64 `json:"id"`
OrderNo string `json:"order_no"`
Title string `json:"title"`
RenterID uint64 `json:"renter_id"`
OwnerID uint64 `json:"owner_id"`
Status string `json:"status"`
RentAmount float64 `json:"rent_amount"`
DepositAmount float64 `json:"deposit_amount"`
CreatedAt time.Time `json:"created_at"`
}
type RecentDisputeDTO struct {
ID uint64 `json:"id"`
OrderID uint64 `json:"order_id"`
OrderNo string `json:"order_no"`
Title string `json:"title"`
Type string `json:"type"`
Status string `json:"status"`
InitiatorID uint64 `json:"initiator_id"`
CreatedAt time.Time `json:"created_at"`
}
@@ -0,0 +1,36 @@
package admindashboard
import (
"errors"
"net/http"
"hfb_sys/backend/pkg/response"
"github.com/gin-gonic/gin"
)
type Handler struct {
service *Service
}
func NewHandler(service *Service) *Handler {
return &Handler{service: service}
}
func (h *Handler) Summary(c *gin.Context) {
item, err := h.service.Summary()
if err != nil {
writeDashboardError(c, err)
return
}
response.OK(c, item)
}
func writeDashboardError(c *gin.Context, err error) {
switch {
case errors.Is(err, ErrDependencyUnavailable):
response.ServiceUnavailable(c, "数据库未连接")
default:
response.Error(c, http.StatusInternalServerError, "internal_error", "后台仪表盘服务暂时不可用")
}
}
@@ -0,0 +1,102 @@
package admindashboard
import (
"time"
"hfb_sys/backend/internal/model"
"gorm.io/gorm"
)
type Repository struct {
db *gorm.DB
}
func NewRepository(db *gorm.DB) *Repository {
return &Repository{db: db}
}
func (r *Repository) Summary() (*DashboardDTO, error) {
now := time.Now()
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
metrics := MetricsDTO{}
pending := PendingDTO{}
if err := r.db.Model(&model.User{}).Count(&metrics.TotalUsers).Error; err != nil {
return nil, err
}
if err := r.db.Model(&model.User{}).Where("realname_status = ?", "verified").Count(&metrics.VerifiedUsers).Error; err != nil {
return nil, err
}
if err := r.db.Model(&model.RentalListing{}).Count(&metrics.TotalListings).Error; err != nil {
return nil, err
}
if err := r.db.Model(&model.RentalListing{}).Where("status = ? AND review_status = ?", "published", "approved").Count(&metrics.PublishedListings).Error; err != nil {
return nil, err
}
if err := r.db.Model(&model.RentalOrder{}).Count(&metrics.TotalOrders).Error; err != nil {
return nil, err
}
if err := r.db.Model(&model.RentalOrder{}).Where("status = ?", "renting").Count(&metrics.RentingOrders).Error; err != nil {
return nil, err
}
if err := r.db.Model(&model.RentalOrder{}).Where("created_at >= ?", today).Count(&metrics.TodayOrders).Error; err != nil {
return nil, err
}
if err := r.db.Model(&model.WalletLedger{}).
Select("COALESCE(SUM(amount), 0)").
Where("created_at >= ?", today).
Scan(&metrics.TodayLedgerAmount).Error; err != nil {
return nil, err
}
if err := r.db.Model(&model.RentalListing{}).Where("review_status = ?", "pending").Count(&pending.ListingReviews).Error; err != nil {
return nil, err
}
if err := r.db.Model(&model.Dispute{}).Where("status IN ?", []string{"open", "processing"}).Count(&pending.Disputes).Error; err != nil {
return nil, err
}
if err := r.db.Model(&model.RentalOrder{}).Where("status = ? AND handoff_status IN ?", "pending_handoff", []string{"pending_owner", "pending_renter_confirm"}).Count(&pending.PendingHandoffs).Error; err != nil {
return nil, err
}
if err := r.db.Model(&model.RentalOrder{}).Where("status = ?", "pending_return_confirm").Count(&pending.PendingReturnConfirms).Error; err != nil {
return nil, err
}
recentOrders, err := r.recentOrders()
if err != nil {
return nil, err
}
recentDisputes, err := r.recentDisputes()
if err != nil {
return nil, err
}
return &DashboardDTO{
Metrics: metrics,
Pending: pending,
RecentOrders: recentOrders,
RecentDisputes: recentDisputes,
GeneratedAt: now,
}, nil
}
func (r *Repository) recentOrders() ([]RecentOrderDTO, error) {
var rows []RecentOrderDTO
err := r.db.Table("rental_orders AS o").
Select("o.id, o.order_no, a.title, o.renter_id, o.owner_id, o.status, o.rent_amount, o.deposit_amount, o.created_at").
Joins("JOIN game_accounts AS a ON a.id = o.account_id").
Order("o.id DESC").
Limit(8).
Scan(&rows).Error
return rows, err
}
func (r *Repository) recentDisputes() ([]RecentDisputeDTO, error) {
var rows []RecentDisputeDTO
err := r.db.Table("disputes AS d").
Select("d.id, d.order_id, o.order_no, a.title, d.type, d.status, d.initiator_id, d.created_at").
Joins("JOIN rental_orders AS o ON o.id = d.order_id").
Joins("JOIN game_accounts AS a ON a.id = o.account_id").
Order("d.id DESC").
Limit(8).
Scan(&rows).Error
return rows, err
}
@@ -0,0 +1,20 @@
package admindashboard
import "errors"
var ErrDependencyUnavailable = errors.New("dependency unavailable")
type Service struct {
repo *Repository
}
func NewService(repo *Repository) *Service {
return &Service{repo: repo}
}
func (s *Service) Summary() (*DashboardDTO, error) {
if s.repo == nil {
return nil, ErrDependencyUnavailable
}
return s.repo.Summary()
}
+8
View File
@@ -5,6 +5,7 @@ import (
"hfb_sys/backend/internal/handler"
"hfb_sys/backend/internal/middleware"
"hfb_sys/backend/internal/modules/adminauth"
"hfb_sys/backend/internal/modules/admindashboard"
"hfb_sys/backend/internal/modules/auth"
"hfb_sys/backend/internal/modules/dispute"
"hfb_sys/backend/internal/modules/listing"
@@ -44,6 +45,12 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
}
adminAuthService := adminauth.NewService(adminAuthRepo)
adminAuthHandler := adminauth.NewHandler(adminAuthService)
var adminDashboardRepo *admindashboard.Repository
if deps.DB != nil {
adminDashboardRepo = admindashboard.NewRepository(deps.DB)
}
adminDashboardService := admindashboard.NewService(adminDashboardRepo)
adminDashboardHandler := admindashboard.NewHandler(adminDashboardService)
userHandler := user.NewHandler(userRepo)
var realnameRepo *realname.Repository
if deps.DB != nil {
@@ -168,6 +175,7 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
{
adminRoutes.GET("/me", adminAuthHandler.Me)
adminRoutes.POST("/auth/logout", adminAuthHandler.Logout)
adminRoutes.GET("/dashboard", adminDashboardHandler.Summary)
adminRoutes.GET("/disputes", disputeHandler.AdminList)
adminRoutes.POST("/disputes/:id/arbitrate", disputeHandler.AdminArbitrate)
adminRoutes.GET("/system-configs", systemConfigHandler.List)