package service import ( "context" "time" "git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global" "git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/dto" "git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/mysqldao" "git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/pkg/errcode" ) // DashboardService 仪表盘统计服务。 type DashboardService struct { statsDao *mysqldao.StatsDao } // NewDashboardService 创建仪表盘服务。 func NewDashboardService() *DashboardService { return &DashboardService{statsDao: mysqldao.NewStatsDao()} } // Summary 聚合仪表盘核心指标:用户/订单/营收/待办。 func (s *DashboardService) Summary(ctx context.Context) (*dto.DashboardResponse, *errcode.Error) { ctx, span := global.Tracer.Start(ctx, "svc.DashboardSummary") defer span.End() todayStart := todayStartUnix() userStats, err := s.statsDao.UserStatsGet(ctx, todayStart) if err != nil { return nil, errcode.DatabaseError } userStatusMap, err := s.statsDao.UserStatsGetByStatus(ctx) if err != nil { return nil, errcode.DatabaseError } orderStats, err := s.statsDao.OrderStatsGet(ctx) if err != nil { return nil, errcode.DatabaseError } revenueStats, err := s.statsDao.RevenueStatsGet(ctx, todayStart) if err != nil { return nil, errcode.DatabaseError } todoStats, err := s.statsDao.TodoStatsGet(ctx) if err != nil { return nil, errcode.DatabaseError } contentStats, err := s.statsDao.ContentStatsGet(ctx) if err != nil { return nil, errcode.DatabaseError } now := nowUnix() vipStats, err := s.statsDao.VIPStatsGet(ctx, now, now+7*86400) if err != nil { return nil, errcode.DatabaseError } goodsStats, err := s.statsDao.GoodsStatsGet(ctx) if err != nil { return nil, errcode.DatabaseError } usageStats, err := s.statsDao.UsageStatsGet(ctx, todayStatDate()) if err != nil { return nil, errcode.DatabaseError } socialStats, err := s.statsDao.SocialStatsGet(ctx) if err != nil { return nil, errcode.DatabaseError } return &dto.DashboardResponse{ Users: dto.DashboardUsers{ Total: userStats.Total, TodayNew: userStats.TodayNew, Normal: userStatusMap[UserStatusNormal], TempBan: userStatusMap[UserStatusTempBan], PermBan: userStatusMap[UserStatusPermBan], Deactivated: userStatusMap[UserStatusDeactivated], }, Orders: dto.DashboardOrders{ Total: orderStats.Total, Pending: orderStats.Pending, Paid: orderStats.Paid, Cancelled: orderStats.Cancelled, Expired: orderStats.Expired, Refunded: orderStats.Refunded, }, Revenue: dto.DashboardRevenue{ TotalRevenue: revenueStats.TotalRevenue, TodayRevenue: revenueStats.TodayRevenue, RefundTotal: revenueStats.RefundTotal, }, Todo: dto.DashboardTodo{ RefundPending: todoStats.RefundPending, // 穿搭口径复用内容概况聚合结果,避免对 outfits 表重复计数。 OutfitPublicTotal: contentStats.OutfitPublic, OutfitBanned: contentStats.OutfitBanned, GoodsOnShelf: todoStats.GoodsOnShelf, }, Content: dto.DashboardContent{ Outfit: dto.DashboardOutfitStats{ Total: contentStats.OutfitTotal, Public: contentStats.OutfitPublic, Private: contentStats.OutfitPrivate, Banned: contentStats.OutfitBanned, }, Clothes: dto.DashboardClothesStats{ Total: contentStats.ClothesTotal, Processing: contentStats.ClothesProcessing, Success: contentStats.ClothesSuccess, Failed: contentStats.ClothesFailed, }, Share: dto.DashboardShareStats{ Total: contentStats.ShareTotal, Normal: contentStats.ShareNormal, Cancelled: contentStats.ShareCancelled, Expired: contentStats.ShareExpired, Banned: contentStats.ShareBanned, }, }, VIP: dto.DashboardVIP{ MemberTotal: vipStats.MemberTotal, Monthly: vipStats.Monthly, Yearly: vipStats.Yearly, Trial: vipStats.Trial, ExpiringSoon: vipStats.ExpiringSoon, }, Goods: dto.DashboardGoods{ Total: goodsStats.Total, OnShelf: goodsStats.OnShelf, OffShelf: goodsStats.OffShelf, ByType: buildGoodsTypeCounts(goodsStats.ByType), }, Usage: dto.DashboardUsage{ AIRecommend: usageStats.AIRecommend, ClosetShare: usageStats.ClosetShare, OutfitShare: usageStats.OutfitShare, }, Social: dto.DashboardSocial{ CosImageCount: socialStats.CosImageCount, LikeCount: socialStats.LikeCount, CollectCount: socialStats.CollectCount, }, }, nil } // buildGoodsTypeCounts 商品类型数量 DAO → DTO。 func buildGoodsTypeCounts(in []mysqldao.GoodsTypeCount) []dto.GoodsTypeCount { out := make([]dto.GoodsTypeCount, 0, len(in)) for _, v := range in { out = append(out, dto.GoodsTypeCount{Type: v.Type, Count: v.Count}) } return out } // Trends 返回近 N 天趋势(新增用户/已支付订单量/营收,按天补零对齐)。 func (s *DashboardService) Trends(ctx context.Context, days int) (*dto.TrendResponse, *errcode.Error) { ctx, span := global.Tracer.Start(ctx, "svc.DashboardTrends") defer span.End() if days <= 0 { days = 30 } if days > 90 { days = 90 } startTs := todayStartUnix() - int64(days-1)*86400 stats, err := s.statsDao.TrendStatsGet(ctx, startTs) if err != nil { return nil, errcode.DatabaseError } loc := time.Now().Location() startDay := time.Unix(startTs, 0).In(loc) points := make([]dto.TrendPoint, 0, days) for i := 0; i < days; i++ { date := startDay.AddDate(0, 0, i).Format("2006-01-02") points = append(points, dto.TrendPoint{ Date: date, NewUsers: stats.NewUsers[date], Orders: stats.Orders[date], Revenue: stats.Revenue[date], }) } return &dto.TrendResponse{Points: points}, nil }