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.GET("/profile", handler.Profile)
|
||||||
protected.POST("/logout", handler.Logout)
|
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