feat: deepen report metrics
This commit is contained in:
@@ -49,12 +49,48 @@ func (s *creator) ReportOverview(
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
// 订单仅统计内容购买类型,并按状态划分已支付/已退款。
|
||||
paidCount, paidAmount, err := s.orderAggregate(ctx, tid, consts.OrderStatusPaid, "paid_at", rg)
|
||||
// 内容规模与互动指标。
|
||||
contentCount, err := s.contentCount(ctx, tid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refundCount, refundAmount, err := s.orderAggregate(ctx, tid, consts.OrderStatusRefunded, "updated_at", rg)
|
||||
contentCreated, err := s.contentCreatedAggregate(ctx, tid, rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
likeActions, err := s.contentActionAggregate(ctx, tid, consts.UserContentActionTypeLike, rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
favoriteActions, err := s.contentActionAggregate(ctx, tid, consts.UserContentActionTypeFavorite, rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commentCount, err := s.commentAggregate(ctx, tid, rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 订单仅统计内容购买类型,并按状态划分已支付/已退款。
|
||||
paidCount, paidAmount, err := s.orderAggregate(ctx, tid, consts.OrderTypeContentPurchase, consts.OrderStatusPaid, "paid_at", rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refundCount, refundAmount, err := s.orderAggregate(ctx, tid, consts.OrderTypeContentPurchase, consts.OrderStatusRefunded, "updated_at", rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 提现维度统计(申请/完成/失败)。
|
||||
withdrawApplyCount, withdrawApplyAmount, err := s.orderAggregate(ctx, tid, consts.OrderTypeWithdrawal, consts.OrderStatusCreated, "created_at", rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
withdrawPaidCount, withdrawPaidAmount, err := s.orderAggregate(ctx, tid, consts.OrderTypeWithdrawal, consts.OrderStatusPaid, "paid_at", rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
withdrawFailedCount, withdrawFailedAmount, err := s.orderAggregate(ctx, tid, consts.OrderTypeWithdrawal, consts.OrderStatusFailed, "updated_at", rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -65,11 +101,39 @@ func (s *creator) ReportOverview(
|
||||
}
|
||||
|
||||
// 生成按日趋势序列。
|
||||
paidSeries, err := s.orderSeries(ctx, tid, consts.OrderStatusPaid, "paid_at", rg)
|
||||
paidSeries, err := s.orderSeries(ctx, tid, consts.OrderTypeContentPurchase, consts.OrderStatusPaid, "paid_at", rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refundSeries, err := s.orderSeries(ctx, tid, consts.OrderStatusRefunded, "updated_at", rg)
|
||||
refundSeries, err := s.orderSeries(ctx, tid, consts.OrderTypeContentPurchase, consts.OrderStatusRefunded, "updated_at", rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
withdrawApplySeries, err := s.orderSeries(ctx, tid, consts.OrderTypeWithdrawal, consts.OrderStatusCreated, "created_at", rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
withdrawPaidSeries, err := s.orderSeries(ctx, tid, consts.OrderTypeWithdrawal, consts.OrderStatusPaid, "paid_at", rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
withdrawFailedSeries, err := s.orderSeries(ctx, tid, consts.OrderTypeWithdrawal, consts.OrderStatusFailed, "updated_at", rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contentCreatedSeries, err := s.contentCreatedSeries(ctx, tid, rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
likeSeries, err := s.contentActionSeries(ctx, tid, consts.UserContentActionTypeLike, rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
favoriteSeries, err := s.contentActionSeries(ctx, tid, consts.UserContentActionTypeFavorite, rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commentSeries, err := s.commentSeries(ctx, tid, rg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -79,23 +143,47 @@ func (s *creator) ReportOverview(
|
||||
key := day.Format("2006-01-02")
|
||||
paidItem := paidSeries[key]
|
||||
refundItem := refundSeries[key]
|
||||
withdrawApplyItem := withdrawApplySeries[key]
|
||||
withdrawPaidItem := withdrawPaidSeries[key]
|
||||
withdrawFailedItem := withdrawFailedSeries[key]
|
||||
items = append(items, creator_dto.ReportOverviewItem{
|
||||
Date: key,
|
||||
PaidOrders: paidItem.Count,
|
||||
PaidAmount: float64(paidItem.Amount) / 100.0,
|
||||
RefundOrders: refundItem.Count,
|
||||
RefundAmount: float64(refundItem.Amount) / 100.0,
|
||||
Date: key,
|
||||
PaidOrders: paidItem.Count,
|
||||
PaidAmount: float64(paidItem.Amount) / 100.0,
|
||||
RefundOrders: refundItem.Count,
|
||||
RefundAmount: float64(refundItem.Amount) / 100.0,
|
||||
WithdrawalApplyOrders: withdrawApplyItem.Count,
|
||||
WithdrawalApplyAmount: float64(withdrawApplyItem.Amount) / 100.0,
|
||||
WithdrawalPaidOrders: withdrawPaidItem.Count,
|
||||
WithdrawalPaidAmount: float64(withdrawPaidItem.Amount) / 100.0,
|
||||
WithdrawalFailedOrders: withdrawFailedItem.Count,
|
||||
WithdrawalFailedAmount: float64(withdrawFailedItem.Amount) / 100.0,
|
||||
ContentCreated: contentCreatedSeries[key],
|
||||
LikeActions: likeSeries[key],
|
||||
FavoriteActions: favoriteSeries[key],
|
||||
CommentCount: commentSeries[key],
|
||||
})
|
||||
}
|
||||
|
||||
return &creator_dto.ReportOverviewResponse{
|
||||
Summary: creator_dto.ReportSummary{
|
||||
TotalViews: totalViews,
|
||||
PaidOrders: paidCount,
|
||||
PaidAmount: float64(paidAmount) / 100.0,
|
||||
RefundOrders: refundCount,
|
||||
RefundAmount: float64(refundAmount) / 100.0,
|
||||
ConversionRate: conversionRate,
|
||||
TotalViews: totalViews,
|
||||
ContentCount: contentCount,
|
||||
ContentCreated: contentCreated,
|
||||
LikeActions: likeActions,
|
||||
FavoriteActions: favoriteActions,
|
||||
CommentCount: commentCount,
|
||||
PaidOrders: paidCount,
|
||||
PaidAmount: float64(paidAmount) / 100.0,
|
||||
RefundOrders: refundCount,
|
||||
RefundAmount: float64(refundAmount) / 100.0,
|
||||
WithdrawalApplyOrders: withdrawApplyCount,
|
||||
WithdrawalApplyAmount: float64(withdrawApplyAmount) / 100.0,
|
||||
WithdrawalPaidOrders: withdrawPaidCount,
|
||||
WithdrawalPaidAmount: float64(withdrawPaidAmount) / 100.0,
|
||||
WithdrawalFailedOrders: withdrawFailedCount,
|
||||
WithdrawalFailedAmount: float64(withdrawFailedAmount) / 100.0,
|
||||
ConversionRate: conversionRate,
|
||||
},
|
||||
Items: items,
|
||||
}, nil
|
||||
@@ -129,7 +217,7 @@ func (s *creator) ExportReport(
|
||||
}
|
||||
|
||||
builder := &strings.Builder{}
|
||||
builder.WriteString("date,paid_orders,paid_amount,refund_orders,refund_amount\n")
|
||||
builder.WriteString("date,paid_orders,paid_amount,refund_orders,refund_amount,withdrawal_apply_orders,withdrawal_apply_amount,withdrawal_paid_orders,withdrawal_paid_amount,withdrawal_failed_orders,withdrawal_failed_amount,content_created,like_actions,favorite_actions,comment_count\n")
|
||||
for _, item := range overview.Items {
|
||||
builder.WriteString(item.Date)
|
||||
builder.WriteString(",")
|
||||
@@ -140,6 +228,26 @@ func (s *creator) ExportReport(
|
||||
builder.WriteString(strconv.FormatInt(item.RefundOrders, 10))
|
||||
builder.WriteString(",")
|
||||
builder.WriteString(formatAmount(item.RefundAmount))
|
||||
builder.WriteString(",")
|
||||
builder.WriteString(strconv.FormatInt(item.WithdrawalApplyOrders, 10))
|
||||
builder.WriteString(",")
|
||||
builder.WriteString(formatAmount(item.WithdrawalApplyAmount))
|
||||
builder.WriteString(",")
|
||||
builder.WriteString(strconv.FormatInt(item.WithdrawalPaidOrders, 10))
|
||||
builder.WriteString(",")
|
||||
builder.WriteString(formatAmount(item.WithdrawalPaidAmount))
|
||||
builder.WriteString(",")
|
||||
builder.WriteString(strconv.FormatInt(item.WithdrawalFailedOrders, 10))
|
||||
builder.WriteString(",")
|
||||
builder.WriteString(formatAmount(item.WithdrawalFailedAmount))
|
||||
builder.WriteString(",")
|
||||
builder.WriteString(strconv.FormatInt(item.ContentCreated, 10))
|
||||
builder.WriteString(",")
|
||||
builder.WriteString(strconv.FormatInt(item.LikeActions, 10))
|
||||
builder.WriteString(",")
|
||||
builder.WriteString(strconv.FormatInt(item.FavoriteActions, 10))
|
||||
builder.WriteString(",")
|
||||
builder.WriteString(strconv.FormatInt(item.CommentCount, 10))
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
|
||||
@@ -160,6 +268,7 @@ type reportAggRow struct {
|
||||
func (s *creator) orderAggregate(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
orderType consts.OrderType,
|
||||
status consts.OrderStatus,
|
||||
timeField string,
|
||||
rg reportRange,
|
||||
@@ -173,7 +282,7 @@ func (s *creator) orderAggregate(
|
||||
Model(&models.Order{}).
|
||||
Select("count(*) as count, coalesce(sum(amount_paid), 0) as amount").
|
||||
Where("tenant_id = ? AND type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
|
||||
tenantID, consts.OrderTypeContentPurchase, status, rg.startDay, rg.endNext).
|
||||
tenantID, orderType, status, rg.startDay, rg.endNext).
|
||||
Scan(&total).Error
|
||||
if err != nil {
|
||||
return 0, 0, errorx.ErrDatabaseError.WithCause(err)
|
||||
@@ -184,6 +293,7 @@ func (s *creator) orderAggregate(
|
||||
func (s *creator) orderSeries(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
orderType consts.OrderType,
|
||||
status consts.OrderStatus,
|
||||
timeField string,
|
||||
rg reportRange,
|
||||
@@ -194,7 +304,7 @@ func (s *creator) orderSeries(
|
||||
Model(&models.Order{}).
|
||||
Select("date_trunc('day', "+timeField+") as day, count(*) as count, coalesce(sum(amount_paid), 0) as amount").
|
||||
Where("tenant_id = ? AND type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
|
||||
tenantID, consts.OrderTypeContentPurchase, status, rg.startDay, rg.endNext).
|
||||
tenantID, orderType, status, rg.startDay, rg.endNext).
|
||||
Group("day").
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
@@ -209,6 +319,127 @@ func (s *creator) orderSeries(
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type reportCountRow struct {
|
||||
Day time.Time `gorm:"column:day"`
|
||||
Count int64 `gorm:"column:count"`
|
||||
}
|
||||
|
||||
func (s *creator) contentCount(ctx context.Context, tenantID int64) (int64, error) {
|
||||
tbl, q := models.ContentQuery.QueryContext(ctx)
|
||||
total, err := q.Where(tbl.TenantID.Eq(tenantID)).Count()
|
||||
if err != nil {
|
||||
return 0, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (s *creator) contentCreatedAggregate(ctx context.Context, tenantID int64, rg reportRange) (int64, error) {
|
||||
tbl, q := models.ContentQuery.QueryContext(ctx)
|
||||
total, err := q.Where(
|
||||
tbl.TenantID.Eq(tenantID),
|
||||
tbl.CreatedAt.Gte(rg.startDay),
|
||||
tbl.CreatedAt.Lt(rg.endNext),
|
||||
).Count()
|
||||
if err != nil {
|
||||
return 0, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (s *creator) contentCreatedSeries(ctx context.Context, tenantID int64, rg reportRange) (map[string]int64, error) {
|
||||
rows := make([]reportCountRow, 0)
|
||||
err := models.ContentQuery.WithContext(ctx).
|
||||
UnderlyingDB().
|
||||
Model(&models.Content{}).
|
||||
Select("date_trunc('day', created_at) as day, count(*) as count").
|
||||
Where("tenant_id = ? AND created_at >= ? AND created_at < ?", tenantID, rg.startDay, rg.endNext).
|
||||
Group("day").
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
return buildCountSeries(rows), nil
|
||||
}
|
||||
|
||||
func (s *creator) contentActionAggregate(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
actionType consts.UserContentActionType,
|
||||
rg reportRange,
|
||||
) (int64, error) {
|
||||
var total int64
|
||||
query := models.UserContentActionQuery.WithContext(ctx).
|
||||
UnderlyingDB().
|
||||
Model(&models.UserContentAction{}).
|
||||
Select("count(*)").
|
||||
Joins("join contents on contents.id = user_content_actions.content_id").
|
||||
Where("user_content_actions.type = ? AND user_content_actions.created_at >= ? AND user_content_actions.created_at < ? AND contents.tenant_id = ?",
|
||||
actionType, rg.startDay, rg.endNext, tenantID)
|
||||
if err := query.Scan(&total).Error; err != nil {
|
||||
return 0, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (s *creator) contentActionSeries(
|
||||
ctx context.Context,
|
||||
tenantID int64,
|
||||
actionType consts.UserContentActionType,
|
||||
rg reportRange,
|
||||
) (map[string]int64, error) {
|
||||
rows := make([]reportCountRow, 0)
|
||||
err := models.UserContentActionQuery.WithContext(ctx).
|
||||
UnderlyingDB().
|
||||
Model(&models.UserContentAction{}).
|
||||
Select("date_trunc('day', user_content_actions.created_at) as day, count(*) as count").
|
||||
Joins("join contents on contents.id = user_content_actions.content_id").
|
||||
Where("user_content_actions.type = ? AND user_content_actions.created_at >= ? AND user_content_actions.created_at < ? AND contents.tenant_id = ?",
|
||||
actionType, rg.startDay, rg.endNext, tenantID).
|
||||
Group("day").
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
return buildCountSeries(rows), nil
|
||||
}
|
||||
|
||||
func (s *creator) commentAggregate(ctx context.Context, tenantID int64, rg reportRange) (int64, error) {
|
||||
tbl, q := models.CommentQuery.QueryContext(ctx)
|
||||
total, err := q.Where(
|
||||
tbl.TenantID.Eq(tenantID),
|
||||
tbl.CreatedAt.Gte(rg.startDay),
|
||||
tbl.CreatedAt.Lt(rg.endNext),
|
||||
).Count()
|
||||
if err != nil {
|
||||
return 0, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (s *creator) commentSeries(ctx context.Context, tenantID int64, rg reportRange) (map[string]int64, error) {
|
||||
rows := make([]reportCountRow, 0)
|
||||
err := models.CommentQuery.WithContext(ctx).
|
||||
UnderlyingDB().
|
||||
Model(&models.Comment{}).
|
||||
Select("date_trunc('day', created_at) as day, count(*) as count").
|
||||
Where("tenant_id = ? AND created_at >= ? AND created_at < ?", tenantID, rg.startDay, rg.endNext).
|
||||
Group("day").
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
return buildCountSeries(rows), nil
|
||||
}
|
||||
|
||||
func buildCountSeries(rows []reportCountRow) map[string]int64 {
|
||||
result := make(map[string]int64, len(rows))
|
||||
for _, row := range rows {
|
||||
key := row.Day.Format("2006-01-02")
|
||||
result[key] = row.Count
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *creator) normalizeReportRange(filter *creator_dto.ReportOverviewFilter) (reportRange, error) {
|
||||
granularity := "day"
|
||||
if filter != nil && filter.Granularity != nil && strings.TrimSpace(*filter.Granularity) != "" {
|
||||
|
||||
Reference in New Issue
Block a user