189 lines
5.5 KiB
Go
189 lines
5.5 KiB
Go
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,
|
|
OutfitPendingAudit: todoStats.OutfitPendingAudit,
|
|
GoodsOnShelf: todoStats.GoodsOnShelf,
|
|
},
|
|
Content: dto.DashboardContent{
|
|
Outfit: dto.DashboardOutfitStats{
|
|
Total: contentStats.OutfitTotal,
|
|
Pending: contentStats.OutfitPending,
|
|
Passed: contentStats.OutfitPassed,
|
|
Rejected: contentStats.OutfitRejected,
|
|
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
|
|
}
|