feat: 完成后台Issue#6 数据统计接口模块
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"wx_service/internal/model"
|
||||
)
|
||||
|
||||
func (h *Handler) StatsOverview(c *gin.Context) {
|
||||
data, err := h.svc.StatsOverview(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "load stats overview failed"))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, model.Success(data))
|
||||
}
|
||||
|
||||
func (h *Handler) StatsMiniPrograms(c *gin.Context) {
|
||||
data, err := h.svc.StatsMiniPrograms(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "load mini-program stats failed"))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, model.Success(data))
|
||||
}
|
||||
|
||||
func (h *Handler) StatsUserGrowth(c *gin.Context) {
|
||||
days, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("days", "7")))
|
||||
data, err := h.svc.StatsUserGrowth(c.Request.Context(), days)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "load user growth failed"))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, model.Success(data))
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
expirymodel "wx_service/internal/expiry"
|
||||
membershipmodel "wx_service/internal/membership/model"
|
||||
"wx_service/internal/model"
|
||||
rmmodel "wx_service/internal/remove_watermark/model"
|
||||
)
|
||||
|
||||
type OverviewStats struct {
|
||||
TotalMiniPrograms int64 `json:"total_mini_programs"`
|
||||
TotalUsers int64 `json:"total_users"`
|
||||
TotalMembers int64 `json:"total_members"`
|
||||
TodayNewUsers int64 `json:"today_new_users"`
|
||||
}
|
||||
|
||||
func (s *Service) StatsOverview(ctx context.Context) (*OverviewStats, error) {
|
||||
var result OverviewStats
|
||||
if err := s.db.WithContext(ctx).Model(&model.MiniProgram{}).Count(&result.TotalMiniPrograms).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Model(&model.User{}).Count(&result.TotalUsers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if err := s.db.WithContext(ctx).Model(&model.UserMembership{}).
|
||||
Where("status = ? AND ends_at > ?", "active", now).
|
||||
Count(&result.TotalMembers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
|
||||
if err := s.db.WithContext(ctx).Model(&model.User{}).
|
||||
Where("created_at >= ?", todayStart).
|
||||
Count(&result.TodayNewUsers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
type MiniProgramStatsItem struct {
|
||||
MiniProgramID uint `json:"mini_program_id"`
|
||||
Name string `json:"name"`
|
||||
UserCount int64 `json:"user_count"`
|
||||
MemberCount int64 `json:"member_count"`
|
||||
DataCount int64 `json:"data_count"`
|
||||
TodayActive int64 `json:"today_active"`
|
||||
}
|
||||
|
||||
func (s *Service) StatsMiniPrograms(ctx context.Context) ([]MiniProgramStatsItem, error) {
|
||||
var miniPrograms []model.MiniProgram
|
||||
if err := s.db.WithContext(ctx).
|
||||
Select("id", "name", "app_id", "description", "created_at", "updated_at").
|
||||
Order("id ASC").
|
||||
Find(&miniPrograms).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(miniPrograms) == 0 {
|
||||
return []MiniProgramStatsItem{}, nil
|
||||
}
|
||||
|
||||
ids := make([]uint, 0, len(miniPrograms))
|
||||
for _, item := range miniPrograms {
|
||||
ids = append(ids, item.ID)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
|
||||
|
||||
userCounts, err := s.groupCountByMiniProgramID(ctx, &model.User{}, ids, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
memberCounts, err := s.groupCountByMiniProgramID(ctx, &model.UserMembership{}, ids, "status = ? AND ends_at > ?", "active", now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
todayUserCounts, err := s.groupCountByMiniProgramID(ctx, &model.User{}, ids, "created_at >= ?", todayStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expiryCounts, err := s.groupCountByMiniProgramID(ctx, &expirymodel.ExpiryItem{}, ids, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
videoCounts, err := s.groupCountByMiniProgramID(ctx, &rmmodel.VideoParseLog{}, ids, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
redeemCounts, err := s.groupCountByMiniProgramID(ctx, &membershipmodel.MembershipRedemption{}, ids, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]MiniProgramStatsItem, 0, len(miniPrograms))
|
||||
for _, item := range miniPrograms {
|
||||
dataCount := expiryCounts[item.ID] + videoCounts[item.ID] + redeemCounts[item.ID]
|
||||
result = append(result, MiniProgramStatsItem{
|
||||
MiniProgramID: item.ID,
|
||||
Name: item.Name,
|
||||
UserCount: userCounts[item.ID],
|
||||
MemberCount: memberCounts[item.ID],
|
||||
DataCount: dataCount,
|
||||
TodayActive: todayUserCounts[item.ID],
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type UserGrowthPoint struct {
|
||||
Date string `json:"date"`
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
|
||||
func (s *Service) StatsUserGrowth(ctx context.Context, days int) ([]UserGrowthPoint, error) {
|
||||
if days <= 0 {
|
||||
days = 7
|
||||
}
|
||||
if days > 90 {
|
||||
days = 90
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
|
||||
startDate := todayStart.AddDate(0, 0, -(days - 1))
|
||||
endDate := todayStart.AddDate(0, 0, 1)
|
||||
|
||||
type row struct {
|
||||
Date string `gorm:"column:date"`
|
||||
Count int64 `gorm:"column:count"`
|
||||
}
|
||||
var rows []row
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.User{}).
|
||||
Select("DATE(created_at) AS date, COUNT(*) AS count").
|
||||
Where("created_at >= ? AND created_at < ?", startDate, endDate).
|
||||
Group("DATE(created_at)").
|
||||
Order("DATE(created_at) ASC").
|
||||
Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
countByDate := make(map[string]int64, len(rows))
|
||||
for _, item := range rows {
|
||||
countByDate[item.Date] = item.Count
|
||||
}
|
||||
|
||||
result := make([]UserGrowthPoint, 0, days)
|
||||
for i := 0; i < days; i++ {
|
||||
date := startDate.AddDate(0, 0, i).Format("2006-01-02")
|
||||
result = append(result, UserGrowthPoint{
|
||||
Date: date,
|
||||
Count: countByDate[date],
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) groupCountByMiniProgramID(ctx context.Context, modelObj interface{}, ids []uint, where string, args ...interface{}) (map[uint]int64, error) {
|
||||
result := make(map[uint]int64)
|
||||
if len(ids) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
query := s.db.WithContext(ctx).
|
||||
Model(modelObj).
|
||||
Select("mini_program_id, COUNT(*) AS count").
|
||||
Where("mini_program_id IN ?", ids)
|
||||
|
||||
if where != "" {
|
||||
query = query.Where(where, args...)
|
||||
}
|
||||
|
||||
var rows []groupedCountRow
|
||||
if err := query.Group("mini_program_id").Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range rows {
|
||||
result[item.MiniProgramID] = item.Count
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -20,6 +20,10 @@ func registerAdminRoutes(router *gin.Engine, handler *adminhandler.Handler) {
|
||||
{
|
||||
protected.GET("/profile", handler.Profile)
|
||||
protected.POST("/logout", handler.Logout)
|
||||
|
||||
protected.GET("/stats/overview", handler.StatsOverview)
|
||||
protected.GET("/stats/mini-programs", handler.StatsMiniPrograms)
|
||||
protected.GET("/stats/user-growth", handler.StatsUserGrowth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user