feat: deepen report metrics

This commit is contained in:
2026-01-15 17:50:37 +08:00
parent ba1d120c84
commit 914df9edf2
10 changed files with 1163 additions and 52 deletions

View File

@@ -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) != "" {