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

@@ -19,6 +19,16 @@ type ReportOverviewResponse struct {
type ReportSummary struct {
// TotalViews 内容累计曝光(全量累计值,用于粗略换算)。
TotalViews int64 `json:"total_views"`
// ContentCount 内容总量(当前快照)。
ContentCount int64 `json:"content_count"`
// ContentCreated 统计区间内新增内容数。
ContentCreated int64 `json:"content_created"`
// LikeActions 统计区间内新增点赞数(基于互动记录)。
LikeActions int64 `json:"like_actions"`
// FavoriteActions 统计区间内新增收藏数(基于互动记录)。
FavoriteActions int64 `json:"favorite_actions"`
// CommentCount 统计区间内新增评论数。
CommentCount int64 `json:"comment_count"`
// PaidOrders 统计区间内已支付订单数。
PaidOrders int64 `json:"paid_orders"`
// PaidAmount 统计区间内已支付金额(单位元)。
@@ -27,6 +37,18 @@ type ReportSummary struct {
RefundOrders int64 `json:"refund_orders"`
// RefundAmount 统计区间内退款金额(单位元)。
RefundAmount float64 `json:"refund_amount"`
// WithdrawalApplyOrders 统计区间内提现申请订单数。
WithdrawalApplyOrders int64 `json:"withdrawal_apply_orders"`
// WithdrawalApplyAmount 统计区间内提现申请金额(单位元)。
WithdrawalApplyAmount float64 `json:"withdrawal_apply_amount"`
// WithdrawalPaidOrders 统计区间内提现完成订单数。
WithdrawalPaidOrders int64 `json:"withdrawal_paid_orders"`
// WithdrawalPaidAmount 统计区间内提现完成金额(单位元)。
WithdrawalPaidAmount float64 `json:"withdrawal_paid_amount"`
// WithdrawalFailedOrders 统计区间内提现失败订单数。
WithdrawalFailedOrders int64 `json:"withdrawal_failed_orders"`
// WithdrawalFailedAmount 统计区间内提现失败金额(单位元)。
WithdrawalFailedAmount float64 `json:"withdrawal_failed_amount"`
// ConversionRate 转化率(已支付订单数 / 累计曝光)。
ConversionRate float64 `json:"conversion_rate"`
}
@@ -42,6 +64,26 @@ type ReportOverviewItem struct {
RefundOrders int64 `json:"refund_orders"`
// RefundAmount 当日退款金额(单位元)。
RefundAmount float64 `json:"refund_amount"`
// WithdrawalApplyOrders 当日提现申请订单数。
WithdrawalApplyOrders int64 `json:"withdrawal_apply_orders"`
// WithdrawalApplyAmount 当日提现申请金额(单位元)。
WithdrawalApplyAmount float64 `json:"withdrawal_apply_amount"`
// WithdrawalPaidOrders 当日提现完成订单数。
WithdrawalPaidOrders int64 `json:"withdrawal_paid_orders"`
// WithdrawalPaidAmount 当日提现完成金额(单位元)。
WithdrawalPaidAmount float64 `json:"withdrawal_paid_amount"`
// WithdrawalFailedOrders 当日提现失败订单数。
WithdrawalFailedOrders int64 `json:"withdrawal_failed_orders"`
// WithdrawalFailedAmount 当日提现失败金额(单位元)。
WithdrawalFailedAmount float64 `json:"withdrawal_failed_amount"`
// ContentCreated 当日新增内容数。
ContentCreated int64 `json:"content_created"`
// LikeActions 当日新增点赞数(基于互动记录)。
LikeActions int64 `json:"like_actions"`
// FavoriteActions 当日新增收藏数(基于互动记录)。
FavoriteActions int64 `json:"favorite_actions"`
// CommentCount 当日新增评论数。
CommentCount int64 `json:"comment_count"`
}
type ReportExportForm struct {

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

View File

@@ -394,6 +394,8 @@ func (s *CreatorTestSuite) Test_ReportOverview() {
models.TableNameUser,
models.TableNameContent,
models.TableNameOrder,
models.TableNameUserContentAction,
models.TableNameComment,
)
owner := &models.User{Username: "owner_r", Phone: "13900001011"}
@@ -405,18 +407,23 @@ func (s *CreatorTestSuite) Test_ReportOverview() {
}
models.TenantQuery.WithContext(ctx).Create(tenant)
models.ContentQuery.WithContext(ctx).Create(&models.Content{
content := &models.Content{
TenantID: tenant.ID,
UserID: owner.ID,
Title: "Content A",
Status: consts.ContentStatusPublished,
Views: 100,
})
}
models.ContentQuery.WithContext(ctx).Create(content)
now := time.Now()
inRangePaidAt := now.Add(-12 * time.Hour)
outRangePaidAt := now.Add(-10 * 24 * time.Hour)
likeAt := now.Add(-2 * time.Hour)
favoriteAt := now.Add(-3 * time.Hour)
commentAt := now.Add(-4 * time.Hour)
models.OrderQuery.WithContext(ctx).Create(
&models.Order{
TenantID: tenant.ID,
@@ -442,8 +449,55 @@ func (s *CreatorTestSuite) Test_ReportOverview() {
AmountPaid: 500,
UpdatedAt: now.Add(-6 * time.Hour),
},
&models.Order{
TenantID: tenant.ID,
UserID: owner.ID,
Type: consts.OrderTypeWithdrawal,
Status: consts.OrderStatusCreated,
AmountPaid: 300,
CreatedAt: now.Add(-5 * time.Hour),
},
&models.Order{
TenantID: tenant.ID,
UserID: owner.ID,
Type: consts.OrderTypeWithdrawal,
Status: consts.OrderStatusPaid,
AmountPaid: 800,
PaidAt: now.Add(-3 * time.Hour),
},
&models.Order{
TenantID: tenant.ID,
UserID: owner.ID,
Type: consts.OrderTypeWithdrawal,
Status: consts.OrderStatusFailed,
AmountPaid: 500,
UpdatedAt: now.Add(-2 * time.Hour),
},
)
models.UserContentActionQuery.WithContext(ctx).Create(
&models.UserContentAction{
UserID: owner.ID,
ContentID: content.ID,
Type: string(consts.UserContentActionTypeLike),
CreatedAt: likeAt,
},
&models.UserContentAction{
UserID: owner.ID,
ContentID: content.ID,
Type: string(consts.UserContentActionTypeFavorite),
CreatedAt: favoriteAt,
},
)
models.CommentQuery.WithContext(ctx).Create(&models.Comment{
TenantID: tenant.ID,
UserID: owner.ID,
ContentID: content.ID,
Content: "Nice",
CreatedAt: commentAt,
})
start := now.Add(-24 * time.Hour).Format(time.RFC3339)
end := now.Format(time.RFC3339)
report, err := Creator.ReportOverview(ctx, tenant.ID, owner.ID, &creator_dto.ReportOverviewFilter{
@@ -452,10 +506,21 @@ func (s *CreatorTestSuite) Test_ReportOverview() {
})
So(err, ShouldBeNil)
So(report.Summary.TotalViews, ShouldEqual, 100)
So(report.Summary.ContentCount, ShouldEqual, 1)
So(report.Summary.ContentCreated, ShouldEqual, 1)
So(report.Summary.LikeActions, ShouldEqual, 1)
So(report.Summary.FavoriteActions, ShouldEqual, 1)
So(report.Summary.CommentCount, ShouldEqual, 1)
So(report.Summary.PaidOrders, ShouldEqual, 1)
So(report.Summary.PaidAmount, ShouldEqual, 10.0)
So(report.Summary.RefundOrders, ShouldEqual, 1)
So(report.Summary.RefundAmount, ShouldEqual, 5.0)
So(report.Summary.WithdrawalApplyOrders, ShouldEqual, 1)
So(report.Summary.WithdrawalApplyAmount, ShouldEqual, 3.0)
So(report.Summary.WithdrawalPaidOrders, ShouldEqual, 1)
So(report.Summary.WithdrawalPaidAmount, ShouldEqual, 8.0)
So(report.Summary.WithdrawalFailedOrders, ShouldEqual, 1)
So(report.Summary.WithdrawalFailedAmount, ShouldEqual, 5.0)
var paidSum, refundSum int64
for _, item := range report.Items {
@@ -475,6 +540,8 @@ func (s *CreatorTestSuite) Test_ExportReport() {
models.TableNameUser,
models.TableNameContent,
models.TableNameOrder,
models.TableNameUserContentAction,
models.TableNameComment,
)
owner := &models.User{Username: "owner_e", Phone: "13900001012"}
@@ -507,6 +574,6 @@ func (s *CreatorTestSuite) Test_ExportReport() {
resp, err := Creator.ExportReport(ctx, tenant.ID, owner.ID, form)
So(err, ShouldBeNil)
So(resp.Filename, ShouldNotBeBlank)
So(resp.Content, ShouldContainSubstring, "date,paid_orders,paid_amount,refund_orders,refund_amount")
So(resp.Content, ShouldContainSubstring, "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")
})
}

View File

@@ -5553,12 +5553,48 @@ func (s *super) ReportOverview(ctx context.Context, filter *super_dto.SuperRepor
return nil, errorx.ErrDatabaseError.WithCause(err)
}
// 订单仅统计内容购买类型,并按状态划分已支付/已退款
paidCount, paidAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderStatusPaid, "paid_at", rg)
// 内容规模与互动指标
contentCount, err := s.contentCount(ctx, tenantID)
if err != nil {
return nil, err
}
refundCount, refundAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderStatusRefunded, "updated_at", rg)
contentCreated, err := s.contentCreatedAggregate(ctx, tenantID, rg)
if err != nil {
return nil, err
}
likeActions, err := s.contentActionAggregate(ctx, tenantID, consts.UserContentActionTypeLike, rg)
if err != nil {
return nil, err
}
favoriteActions, err := s.contentActionAggregate(ctx, tenantID, consts.UserContentActionTypeFavorite, rg)
if err != nil {
return nil, err
}
commentCount, err := s.commentAggregate(ctx, tenantID, rg)
if err != nil {
return nil, err
}
// 订单仅统计内容购买类型,并按状态划分已支付/已退款。
paidCount, paidAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderTypeContentPurchase, consts.OrderStatusPaid, "paid_at", rg)
if err != nil {
return nil, err
}
refundCount, refundAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderTypeContentPurchase, consts.OrderStatusRefunded, "updated_at", rg)
if err != nil {
return nil, err
}
// 提现维度统计(申请/完成/失败)。
withdrawApplyCount, withdrawApplyAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderTypeWithdrawal, consts.OrderStatusCreated, "created_at", rg)
if err != nil {
return nil, err
}
withdrawPaidCount, withdrawPaidAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderTypeWithdrawal, consts.OrderStatusPaid, "paid_at", rg)
if err != nil {
return nil, err
}
withdrawFailedCount, withdrawFailedAmount, err := s.reportOrderAggregate(ctx, tenantID, consts.OrderTypeWithdrawal, consts.OrderStatusFailed, "updated_at", rg)
if err != nil {
return nil, err
}
@@ -5568,11 +5604,39 @@ func (s *super) ReportOverview(ctx context.Context, filter *super_dto.SuperRepor
conversionRate = float64(paidCount) / float64(totalViews)
}
paidSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderStatusPaid, "paid_at", rg)
paidSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderTypeContentPurchase, consts.OrderStatusPaid, "paid_at", rg)
if err != nil {
return nil, err
}
refundSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderStatusRefunded, "updated_at", rg)
refundSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderTypeContentPurchase, consts.OrderStatusRefunded, "updated_at", rg)
if err != nil {
return nil, err
}
withdrawApplySeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderTypeWithdrawal, consts.OrderStatusCreated, "created_at", rg)
if err != nil {
return nil, err
}
withdrawPaidSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderTypeWithdrawal, consts.OrderStatusPaid, "paid_at", rg)
if err != nil {
return nil, err
}
withdrawFailedSeries, err := s.reportOrderSeries(ctx, tenantID, consts.OrderTypeWithdrawal, consts.OrderStatusFailed, "updated_at", rg)
if err != nil {
return nil, err
}
contentCreatedSeries, err := s.contentCreatedSeries(ctx, tenantID, rg)
if err != nil {
return nil, err
}
likeSeries, err := s.contentActionSeries(ctx, tenantID, consts.UserContentActionTypeLike, rg)
if err != nil {
return nil, err
}
favoriteSeries, err := s.contentActionSeries(ctx, tenantID, consts.UserContentActionTypeFavorite, rg)
if err != nil {
return nil, err
}
commentSeries, err := s.commentSeries(ctx, tenantID, rg)
if err != nil {
return nil, err
}
@@ -5582,23 +5646,47 @@ func (s *super) ReportOverview(ctx context.Context, filter *super_dto.SuperRepor
key := day.Format("2006-01-02")
paidItem := paidSeries[key]
refundItem := refundSeries[key]
withdrawApplyItem := withdrawApplySeries[key]
withdrawPaidItem := withdrawPaidSeries[key]
withdrawFailedItem := withdrawFailedSeries[key]
items = append(items, v1_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 &v1_dto.ReportOverviewResponse{
Summary: v1_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
@@ -5627,7 +5715,7 @@ func (s *super) ExportReport(ctx context.Context, form *super_dto.SuperReportExp
}
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(",")
@@ -5638,6 +5726,26 @@ func (s *super) ExportReport(ctx context.Context, form *super_dto.SuperReportExp
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")
}
@@ -5649,9 +5757,126 @@ func (s *super) ExportReport(ctx context.Context, form *super_dto.SuperReportExp
}, nil
}
func (s *super) contentCount(ctx context.Context, tenantID int64) (int64, error) {
tbl, q := models.ContentQuery.QueryContext(ctx)
if tenantID > 0 {
q = q.Where(tbl.TenantID.Eq(tenantID))
}
total, err := q.Count()
if err != nil {
return 0, errorx.ErrDatabaseError.WithCause(err)
}
return total, nil
}
func (s *super) contentCreatedAggregate(ctx context.Context, tenantID int64, rg reportRange) (int64, error) {
tbl, q := models.ContentQuery.QueryContext(ctx)
q = q.Where(tbl.CreatedAt.Gte(rg.startDay), tbl.CreatedAt.Lt(rg.endNext))
if tenantID > 0 {
q = q.Where(tbl.TenantID.Eq(tenantID))
}
total, err := q.Count()
if err != nil {
return 0, errorx.ErrDatabaseError.WithCause(err)
}
return total, nil
}
func (s *super) contentCreatedSeries(ctx context.Context, tenantID int64, rg reportRange) (map[string]int64, error) {
rows := make([]reportCountRow, 0)
query := models.ContentQuery.WithContext(ctx).
UnderlyingDB().
Model(&models.Content{}).
Select("date_trunc('day', created_at) as day, count(*) as count").
Where("created_at >= ? AND created_at < ?", rg.startDay, rg.endNext)
if tenantID > 0 {
query = query.Where("tenant_id = ?", tenantID)
}
if err := query.Group("day").Scan(&rows).Error; err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return buildCountSeries(rows), nil
}
func (s *super) 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(*)").
Where("user_content_actions.type = ? AND user_content_actions.created_at >= ? AND user_content_actions.created_at < ?",
actionType, rg.startDay, rg.endNext)
if tenantID > 0 {
query = query.Joins("join contents on contents.id = user_content_actions.content_id").
Where("contents.tenant_id = ?", tenantID)
}
if err := query.Scan(&total).Error; err != nil {
return 0, errorx.ErrDatabaseError.WithCause(err)
}
return total, nil
}
func (s *super) contentActionSeries(
ctx context.Context,
tenantID int64,
actionType consts.UserContentActionType,
rg reportRange,
) (map[string]int64, error) {
rows := make([]reportCountRow, 0)
query := models.UserContentActionQuery.WithContext(ctx).
UnderlyingDB().
Model(&models.UserContentAction{}).
Select("date_trunc('day', user_content_actions.created_at) as day, count(*) as count").
Where("user_content_actions.type = ? AND user_content_actions.created_at >= ? AND user_content_actions.created_at < ?",
actionType, rg.startDay, rg.endNext)
if tenantID > 0 {
query = query.Joins("join contents on contents.id = user_content_actions.content_id").
Where("contents.tenant_id = ?", tenantID)
}
if err := query.Group("day").Scan(&rows).Error; err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return buildCountSeries(rows), nil
}
func (s *super) commentAggregate(ctx context.Context, tenantID int64, rg reportRange) (int64, error) {
tbl, q := models.CommentQuery.QueryContext(ctx)
q = q.Where(tbl.CreatedAt.Gte(rg.startDay), tbl.CreatedAt.Lt(rg.endNext))
if tenantID > 0 {
q = q.Where(tbl.TenantID.Eq(tenantID))
}
total, err := q.Count()
if err != nil {
return 0, errorx.ErrDatabaseError.WithCause(err)
}
return total, nil
}
func (s *super) commentSeries(ctx context.Context, tenantID int64, rg reportRange) (map[string]int64, error) {
rows := make([]reportCountRow, 0)
query := models.CommentQuery.WithContext(ctx).
UnderlyingDB().
Model(&models.Comment{}).
Select("date_trunc('day', created_at) as day, count(*) as count").
Where("created_at >= ? AND created_at < ?", rg.startDay, rg.endNext)
if tenantID > 0 {
query = query.Where("tenant_id = ?", tenantID)
}
if err := query.Group("day").Scan(&rows).Error; err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return buildCountSeries(rows), nil
}
func (s *super) reportOrderAggregate(
ctx context.Context,
tenantID int64,
orderType consts.OrderType,
status consts.OrderStatus,
timeField string,
rg reportRange,
@@ -5666,7 +5891,7 @@ func (s *super) reportOrderAggregate(
Model(&models.Order{}).
Select("count(*) as count, coalesce(sum(amount_paid), 0) as amount").
Where("type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
consts.OrderTypeContentPurchase, status, rg.startDay, rg.endNext)
orderType, status, rg.startDay, rg.endNext)
if tenantID > 0 {
query = query.Where("tenant_id = ?", tenantID)
}
@@ -5680,6 +5905,7 @@ func (s *super) reportOrderAggregate(
func (s *super) reportOrderSeries(
ctx context.Context,
tenantID int64,
orderType consts.OrderType,
status consts.OrderStatus,
timeField string,
rg reportRange,
@@ -5690,7 +5916,7 @@ func (s *super) reportOrderSeries(
Model(&models.Order{}).
Select("date_trunc('day', "+timeField+") as day, count(*) as count, coalesce(sum(amount_paid), 0) as amount").
Where("type = ? AND status = ? AND "+timeField+" >= ? AND "+timeField+" < ?",
consts.OrderTypeContentPurchase, status, rg.startDay, rg.endNext)
orderType, status, rg.startDay, rg.endNext)
if tenantID > 0 {
query = query.Where("tenant_id = ?", tenantID)
}