feat: add creator report overview export
This commit is contained in:
@@ -69,6 +69,50 @@ func (c *Creator) CreateMemberInvite(
|
|||||||
return services.Tenant.CreateInvite(ctx, tenantID, user.ID, form)
|
return services.Tenant.CreateInvite(ctx, tenantID, user.ID, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get report overview
|
||||||
|
//
|
||||||
|
// @Router /t/:tenantCode/v1/creator/reports/overview [get]
|
||||||
|
// @Summary Report overview
|
||||||
|
// @Description Get creator report overview
|
||||||
|
// @Tags CreatorCenter
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param start_at query string false "Start time (RFC3339)"
|
||||||
|
// @Param end_at query string false "End time (RFC3339)"
|
||||||
|
// @Param granularity query string false "Granularity (day)"
|
||||||
|
// @Success 200 {object} dto.ReportOverviewResponse
|
||||||
|
// @Bind user local key(__ctx_user)
|
||||||
|
// @Bind filter query
|
||||||
|
func (c *Creator) ReportOverview(
|
||||||
|
ctx fiber.Ctx,
|
||||||
|
user *models.User,
|
||||||
|
filter *dto.ReportOverviewFilter,
|
||||||
|
) (*dto.ReportOverviewResponse, error) {
|
||||||
|
tenantID := getTenantID(ctx)
|
||||||
|
return services.Creator.ReportOverview(ctx, tenantID, user.ID, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export report overview
|
||||||
|
//
|
||||||
|
// @Router /t/:tenantCode/v1/creator/reports/export [post]
|
||||||
|
// @Summary Export report overview
|
||||||
|
// @Description Export creator report overview
|
||||||
|
// @Tags CreatorCenter
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param form body dto.ReportExportForm true "Export form"
|
||||||
|
// @Success 200 {object} dto.ReportExportResponse
|
||||||
|
// @Bind user local key(__ctx_user)
|
||||||
|
// @Bind form body
|
||||||
|
func (c *Creator) ExportReport(
|
||||||
|
ctx fiber.Ctx,
|
||||||
|
user *models.User,
|
||||||
|
form *dto.ReportExportForm,
|
||||||
|
) (*dto.ReportExportResponse, error) {
|
||||||
|
tenantID := getTenantID(ctx)
|
||||||
|
return services.Creator.ExportReport(ctx, tenantID, user.ID, form)
|
||||||
|
}
|
||||||
|
|
||||||
// Get creator dashboard stats
|
// Get creator dashboard stats
|
||||||
//
|
//
|
||||||
// @Router /t/:tenantCode/v1/creator/dashboard [get]
|
// @Router /t/:tenantCode/v1/creator/dashboard [get]
|
||||||
|
|||||||
65
backend/app/http/v1/dto/creator_report.go
Normal file
65
backend/app/http/v1/dto/creator_report.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
type ReportOverviewFilter struct {
|
||||||
|
// StartAt 统计开始时间(RFC3339,可选;默认当前时间往前 7 天)。
|
||||||
|
StartAt *string `query:"start_at"`
|
||||||
|
// EndAt 统计结束时间(RFC3339,可选;默认当前时间)。
|
||||||
|
EndAt *string `query:"end_at"`
|
||||||
|
// Granularity 统计粒度(day;目前仅支持 day)。
|
||||||
|
Granularity *string `query:"granularity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportOverviewResponse struct {
|
||||||
|
// Summary 汇总指标。
|
||||||
|
Summary ReportSummary `json:"summary"`
|
||||||
|
// Items 按日期拆分的趋势数据。
|
||||||
|
Items []ReportOverviewItem `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportSummary struct {
|
||||||
|
// TotalViews 内容累计曝光(全量累计值,用于粗略换算)。
|
||||||
|
TotalViews int64 `json:"total_views"`
|
||||||
|
// PaidOrders 统计区间内已支付订单数。
|
||||||
|
PaidOrders int64 `json:"paid_orders"`
|
||||||
|
// PaidAmount 统计区间内已支付金额(单位元)。
|
||||||
|
PaidAmount float64 `json:"paid_amount"`
|
||||||
|
// RefundOrders 统计区间内退款订单数。
|
||||||
|
RefundOrders int64 `json:"refund_orders"`
|
||||||
|
// RefundAmount 统计区间内退款金额(单位元)。
|
||||||
|
RefundAmount float64 `json:"refund_amount"`
|
||||||
|
// ConversionRate 转化率(已支付订单数 / 累计曝光)。
|
||||||
|
ConversionRate float64 `json:"conversion_rate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportOverviewItem struct {
|
||||||
|
// Date 日期(YYYY-MM-DD)。
|
||||||
|
Date string `json:"date"`
|
||||||
|
// PaidOrders 当日已支付订单数。
|
||||||
|
PaidOrders int64 `json:"paid_orders"`
|
||||||
|
// PaidAmount 当日已支付金额(单位元)。
|
||||||
|
PaidAmount float64 `json:"paid_amount"`
|
||||||
|
// RefundOrders 当日退款订单数。
|
||||||
|
RefundOrders int64 `json:"refund_orders"`
|
||||||
|
// RefundAmount 当日退款金额(单位元)。
|
||||||
|
RefundAmount float64 `json:"refund_amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportExportForm struct {
|
||||||
|
// StartAt 统计开始时间(RFC3339,可选;默认当前时间往前 7 天)。
|
||||||
|
StartAt *string `json:"start_at"`
|
||||||
|
// EndAt 统计结束时间(RFC3339,可选;默认当前时间)。
|
||||||
|
EndAt *string `json:"end_at"`
|
||||||
|
// Granularity 统计粒度(day;目前仅支持 day)。
|
||||||
|
Granularity *string `json:"granularity"`
|
||||||
|
// Format 导出格式(仅支持 csv)。
|
||||||
|
Format string `json:"format"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportExportResponse struct {
|
||||||
|
// Filename 导出文件名。
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
// MimeType 导出内容类型。
|
||||||
|
MimeType string `json:"mime_type"`
|
||||||
|
// Content 导出内容(CSV 文本)。
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
@@ -191,6 +191,12 @@ func (r *Routes) Register(router fiber.Router) {
|
|||||||
r.creator.ListPayoutAccounts,
|
r.creator.ListPayoutAccounts,
|
||||||
Local[*models.User]("__ctx_user"),
|
Local[*models.User]("__ctx_user"),
|
||||||
))
|
))
|
||||||
|
r.log.Debugf("Registering route: Get /t/:tenantCode/v1/creator/reports/overview -> creator.ReportOverview")
|
||||||
|
router.Get("/t/:tenantCode/v1/creator/reports/overview"[len(r.Path()):], DataFunc2(
|
||||||
|
r.creator.ReportOverview,
|
||||||
|
Local[*models.User]("__ctx_user"),
|
||||||
|
Query[dto.ReportOverviewFilter]("filter"),
|
||||||
|
))
|
||||||
r.log.Debugf("Registering route: Get /t/:tenantCode/v1/creator/settings -> creator.GetSettings")
|
r.log.Debugf("Registering route: Get /t/:tenantCode/v1/creator/settings -> creator.GetSettings")
|
||||||
router.Get("/t/:tenantCode/v1/creator/settings"[len(r.Path()):], DataFunc1(
|
router.Get("/t/:tenantCode/v1/creator/settings"[len(r.Path()):], DataFunc1(
|
||||||
r.creator.GetSettings,
|
r.creator.GetSettings,
|
||||||
@@ -234,6 +240,12 @@ func (r *Routes) Register(router fiber.Router) {
|
|||||||
Local[*models.User]("__ctx_user"),
|
Local[*models.User]("__ctx_user"),
|
||||||
Body[dto.PayoutAccount]("form"),
|
Body[dto.PayoutAccount]("form"),
|
||||||
))
|
))
|
||||||
|
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/creator/reports/export -> creator.ExportReport")
|
||||||
|
router.Post("/t/:tenantCode/v1/creator/reports/export"[len(r.Path()):], DataFunc2(
|
||||||
|
r.creator.ExportReport,
|
||||||
|
Local[*models.User]("__ctx_user"),
|
||||||
|
Body[dto.ReportExportForm]("form"),
|
||||||
|
))
|
||||||
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/creator/withdraw -> creator.Withdraw")
|
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/creator/withdraw -> creator.Withdraw")
|
||||||
router.Post("/t/:tenantCode/v1/creator/withdraw"[len(r.Path()):], Func2(
|
router.Post("/t/:tenantCode/v1/creator/withdraw"[len(r.Path()):], Func2(
|
||||||
r.creator.Withdraw,
|
r.creator.Withdraw,
|
||||||
|
|||||||
256
backend/app/services/creator_report.go
Normal file
256
backend/app/services/creator_report.go
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"quyun/v2/app/errorx"
|
||||||
|
creator_dto "quyun/v2/app/http/v1/dto"
|
||||||
|
"quyun/v2/database/models"
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
|
)
|
||||||
|
|
||||||
|
type reportRange struct {
|
||||||
|
startDay time.Time
|
||||||
|
endDay time.Time
|
||||||
|
endNext time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *creator) ReportOverview(
|
||||||
|
ctx context.Context,
|
||||||
|
tenantID int64,
|
||||||
|
userID int64,
|
||||||
|
filter *creator_dto.ReportOverviewFilter,
|
||||||
|
) (*creator_dto.ReportOverviewResponse, error) {
|
||||||
|
// 校验租户归属(仅租户主可查看统计)。
|
||||||
|
tid, err := s.getTenantID(ctx, tenantID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统一统计时间范围与粒度。
|
||||||
|
rg, err := s.normalizeReportRange(filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计累计曝光(全量累计值,暂无按时间拆分的曝光记录)。
|
||||||
|
var totalViews int64
|
||||||
|
err = models.ContentQuery.WithContext(ctx).
|
||||||
|
UnderlyingDB().
|
||||||
|
Model(&models.Content{}).
|
||||||
|
Select("coalesce(sum(views), 0)").
|
||||||
|
Where("tenant_id = ?", tid).
|
||||||
|
Scan(&totalViews).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单仅统计内容购买类型,并按状态划分已支付/已退款。
|
||||||
|
paidCount, paidAmount, err := s.orderAggregate(ctx, tid, consts.OrderStatusPaid, "paid_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
refundCount, refundAmount, err := s.orderAggregate(ctx, tid, consts.OrderStatusRefunded, "updated_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conversionRate := 0.0
|
||||||
|
if totalViews > 0 {
|
||||||
|
conversionRate = float64(paidCount) / float64(totalViews)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成按日趋势序列。
|
||||||
|
paidSeries, err := s.orderSeries(ctx, tid, consts.OrderStatusPaid, "paid_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
refundSeries, err := s.orderSeries(ctx, tid, consts.OrderStatusRefunded, "updated_at", rg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
items := make([]creator_dto.ReportOverviewItem, 0)
|
||||||
|
for day := rg.startDay; !day.After(rg.endDay); day = day.AddDate(0, 0, 1) {
|
||||||
|
key := day.Format("2006-01-02")
|
||||||
|
paidItem := paidSeries[key]
|
||||||
|
refundItem := refundSeries[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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
Items: items,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *creator) ExportReport(
|
||||||
|
ctx context.Context,
|
||||||
|
tenantID int64,
|
||||||
|
userID int64,
|
||||||
|
form *creator_dto.ReportExportForm,
|
||||||
|
) (*creator_dto.ReportExportResponse, error) {
|
||||||
|
if form == nil {
|
||||||
|
return nil, errorx.ErrBadRequest.WithMsg("导出参数不能为空")
|
||||||
|
}
|
||||||
|
format := strings.ToLower(strings.TrimSpace(form.Format))
|
||||||
|
if format == "" {
|
||||||
|
format = "csv"
|
||||||
|
}
|
||||||
|
if format != "csv" {
|
||||||
|
return nil, errorx.ErrBadRequest.WithMsg("仅支持 CSV 导出")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出逻辑复用概览统计,保证口径一致。
|
||||||
|
overview, err := s.ReportOverview(ctx, tenantID, userID, &creator_dto.ReportOverviewFilter{
|
||||||
|
StartAt: form.StartAt,
|
||||||
|
EndAt: form.EndAt,
|
||||||
|
Granularity: form.Granularity,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := &strings.Builder{}
|
||||||
|
builder.WriteString("date,paid_orders,paid_amount,refund_orders,refund_amount\n")
|
||||||
|
for _, item := range overview.Items {
|
||||||
|
builder.WriteString(item.Date)
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.PaidOrders, 10))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(formatAmount(item.PaidAmount))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(strconv.FormatInt(item.RefundOrders, 10))
|
||||||
|
builder.WriteString(",")
|
||||||
|
builder.WriteString(formatAmount(item.RefundAmount))
|
||||||
|
builder.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("report_overview_%s.csv", time.Now().Format("20060102_150405"))
|
||||||
|
return &creator_dto.ReportExportResponse{
|
||||||
|
Filename: filename,
|
||||||
|
MimeType: "text/csv",
|
||||||
|
Content: builder.String(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type reportAggRow struct {
|
||||||
|
Day time.Time `gorm:"column:day"`
|
||||||
|
Count int64 `gorm:"column:count"`
|
||||||
|
Amount int64 `gorm:"column:amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *creator) orderAggregate(
|
||||||
|
ctx context.Context,
|
||||||
|
tenantID int64,
|
||||||
|
status consts.OrderStatus,
|
||||||
|
timeField string,
|
||||||
|
rg reportRange,
|
||||||
|
) (int64, int64, error) {
|
||||||
|
var total struct {
|
||||||
|
Count int64 `gorm:"column:count"`
|
||||||
|
Amount int64 `gorm:"column:amount"`
|
||||||
|
}
|
||||||
|
err := models.OrderQuery.WithContext(ctx).
|
||||||
|
UnderlyingDB().
|
||||||
|
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).
|
||||||
|
Scan(&total).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
return total.Count, total.Amount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *creator) orderSeries(
|
||||||
|
ctx context.Context,
|
||||||
|
tenantID int64,
|
||||||
|
status consts.OrderStatus,
|
||||||
|
timeField string,
|
||||||
|
rg reportRange,
|
||||||
|
) (map[string]reportAggRow, error) {
|
||||||
|
rows := make([]reportAggRow, 0)
|
||||||
|
err := models.OrderQuery.WithContext(ctx).
|
||||||
|
UnderlyingDB().
|
||||||
|
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).
|
||||||
|
Group("day").
|
||||||
|
Scan(&rows).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[string]reportAggRow, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
key := row.Day.Format("2006-01-02")
|
||||||
|
result[key] = row
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *creator) normalizeReportRange(filter *creator_dto.ReportOverviewFilter) (reportRange, error) {
|
||||||
|
granularity := "day"
|
||||||
|
if filter != nil && filter.Granularity != nil && strings.TrimSpace(*filter.Granularity) != "" {
|
||||||
|
granularity = strings.ToLower(strings.TrimSpace(*filter.Granularity))
|
||||||
|
}
|
||||||
|
if granularity != "day" {
|
||||||
|
return reportRange{}, errorx.ErrBadRequest.WithMsg("仅支持按天统计")
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
endAt := now
|
||||||
|
if filter != nil && filter.EndAt != nil && strings.TrimSpace(*filter.EndAt) != "" {
|
||||||
|
parsed, err := time.Parse(time.RFC3339, strings.TrimSpace(*filter.EndAt))
|
||||||
|
if err != nil {
|
||||||
|
return reportRange{}, errorx.ErrBadRequest.WithMsg("结束时间格式错误")
|
||||||
|
}
|
||||||
|
endAt = parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
startAt := endAt.AddDate(0, 0, -6)
|
||||||
|
if filter != nil && filter.StartAt != nil && strings.TrimSpace(*filter.StartAt) != "" {
|
||||||
|
parsed, err := time.Parse(time.RFC3339, strings.TrimSpace(*filter.StartAt))
|
||||||
|
if err != nil {
|
||||||
|
return reportRange{}, errorx.ErrBadRequest.WithMsg("开始时间格式错误")
|
||||||
|
}
|
||||||
|
startAt = parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
startDay := time.Date(startAt.Year(), startAt.Month(), startAt.Day(), 0, 0, 0, 0, startAt.Location())
|
||||||
|
endDay := time.Date(endAt.Year(), endAt.Month(), endAt.Day(), 0, 0, 0, 0, endAt.Location())
|
||||||
|
if endDay.Before(startDay) {
|
||||||
|
return reportRange{}, errorx.ErrBadRequest.WithMsg("结束时间不能早于开始时间")
|
||||||
|
}
|
||||||
|
|
||||||
|
endNext := endDay.AddDate(0, 0, 1)
|
||||||
|
return reportRange{
|
||||||
|
startDay: startDay,
|
||||||
|
endDay: endDay,
|
||||||
|
endNext: endNext,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatAmount(amount float64) string {
|
||||||
|
return strconv.FormatFloat(amount, 'f', 2, 64)
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"quyun/v2/app/commands/testx"
|
"quyun/v2/app/commands/testx"
|
||||||
creator_dto "quyun/v2/app/http/v1/dto"
|
creator_dto "quyun/v2/app/http/v1/dto"
|
||||||
@@ -384,3 +385,128 @@ func (s *CreatorTestSuite) Test_Refund() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *CreatorTestSuite) Test_ReportOverview() {
|
||||||
|
Convey("ReportOverview", s.T(), func() {
|
||||||
|
ctx := s.T().Context()
|
||||||
|
database.Truncate(ctx, s.DB,
|
||||||
|
models.TableNameTenant,
|
||||||
|
models.TableNameUser,
|
||||||
|
models.TableNameContent,
|
||||||
|
models.TableNameOrder,
|
||||||
|
)
|
||||||
|
|
||||||
|
owner := &models.User{Username: "owner_r", Phone: "13900001011"}
|
||||||
|
models.UserQuery.WithContext(ctx).Create(owner)
|
||||||
|
tenant := &models.Tenant{
|
||||||
|
Name: "Tenant Report",
|
||||||
|
UserID: owner.ID,
|
||||||
|
Status: consts.TenantStatusVerified,
|
||||||
|
}
|
||||||
|
models.TenantQuery.WithContext(ctx).Create(tenant)
|
||||||
|
|
||||||
|
models.ContentQuery.WithContext(ctx).Create(&models.Content{
|
||||||
|
TenantID: tenant.ID,
|
||||||
|
UserID: owner.ID,
|
||||||
|
Title: "Content A",
|
||||||
|
Status: consts.ContentStatusPublished,
|
||||||
|
Views: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
inRangePaidAt := now.Add(-12 * time.Hour)
|
||||||
|
outRangePaidAt := now.Add(-10 * 24 * time.Hour)
|
||||||
|
|
||||||
|
models.OrderQuery.WithContext(ctx).Create(
|
||||||
|
&models.Order{
|
||||||
|
TenantID: tenant.ID,
|
||||||
|
UserID: owner.ID,
|
||||||
|
Type: consts.OrderTypeContentPurchase,
|
||||||
|
Status: consts.OrderStatusPaid,
|
||||||
|
AmountPaid: 1000,
|
||||||
|
PaidAt: inRangePaidAt,
|
||||||
|
},
|
||||||
|
&models.Order{
|
||||||
|
TenantID: tenant.ID,
|
||||||
|
UserID: owner.ID,
|
||||||
|
Type: consts.OrderTypeContentPurchase,
|
||||||
|
Status: consts.OrderStatusPaid,
|
||||||
|
AmountPaid: 2000,
|
||||||
|
PaidAt: outRangePaidAt,
|
||||||
|
},
|
||||||
|
&models.Order{
|
||||||
|
TenantID: tenant.ID,
|
||||||
|
UserID: owner.ID,
|
||||||
|
Type: consts.OrderTypeContentPurchase,
|
||||||
|
Status: consts.OrderStatusRefunded,
|
||||||
|
AmountPaid: 500,
|
||||||
|
UpdatedAt: now.Add(-6 * time.Hour),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
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{
|
||||||
|
StartAt: &start,
|
||||||
|
EndAt: &end,
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(report.Summary.TotalViews, ShouldEqual, 100)
|
||||||
|
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)
|
||||||
|
|
||||||
|
var paidSum, refundSum int64
|
||||||
|
for _, item := range report.Items {
|
||||||
|
paidSum += item.PaidOrders
|
||||||
|
refundSum += item.RefundOrders
|
||||||
|
}
|
||||||
|
So(paidSum, ShouldEqual, report.Summary.PaidOrders)
|
||||||
|
So(refundSum, ShouldEqual, report.Summary.RefundOrders)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CreatorTestSuite) Test_ExportReport() {
|
||||||
|
Convey("ExportReport", s.T(), func() {
|
||||||
|
ctx := s.T().Context()
|
||||||
|
database.Truncate(ctx, s.DB,
|
||||||
|
models.TableNameTenant,
|
||||||
|
models.TableNameUser,
|
||||||
|
models.TableNameContent,
|
||||||
|
models.TableNameOrder,
|
||||||
|
)
|
||||||
|
|
||||||
|
owner := &models.User{Username: "owner_e", Phone: "13900001012"}
|
||||||
|
models.UserQuery.WithContext(ctx).Create(owner)
|
||||||
|
tenant := &models.Tenant{
|
||||||
|
Name: "Tenant Export",
|
||||||
|
UserID: owner.ID,
|
||||||
|
Status: consts.TenantStatusVerified,
|
||||||
|
}
|
||||||
|
models.TenantQuery.WithContext(ctx).Create(tenant)
|
||||||
|
|
||||||
|
models.ContentQuery.WithContext(ctx).Create(&models.Content{
|
||||||
|
TenantID: tenant.ID,
|
||||||
|
UserID: owner.ID,
|
||||||
|
Title: "Content Export",
|
||||||
|
Status: consts.ContentStatusPublished,
|
||||||
|
Views: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
models.OrderQuery.WithContext(ctx).Create(&models.Order{
|
||||||
|
TenantID: tenant.ID,
|
||||||
|
UserID: owner.ID,
|
||||||
|
Type: consts.OrderTypeContentPurchase,
|
||||||
|
Status: consts.OrderStatusPaid,
|
||||||
|
AmountPaid: 1200,
|
||||||
|
PaidAt: time.Now().Add(-2 * time.Hour),
|
||||||
|
})
|
||||||
|
|
||||||
|
form := &creator_dto.ReportExportForm{Format: "csv"}
|
||||||
|
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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1884,6 +1884,83 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/t/{tenantCode}/v1/creator/reports/export": {
|
||||||
|
"post": {
|
||||||
|
"description": "Export creator report overview",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"CreatorCenter"
|
||||||
|
],
|
||||||
|
"summary": "Export report overview",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Export form",
|
||||||
|
"name": "form",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.ReportExportForm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.ReportExportResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/t/{tenantCode}/v1/creator/reports/overview": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get creator report overview",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"CreatorCenter"
|
||||||
|
],
|
||||||
|
"summary": "Report overview",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Start time (RFC3339)",
|
||||||
|
"name": "start_at",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "End time (RFC3339)",
|
||||||
|
"name": "end_at",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Granularity (day)",
|
||||||
|
"name": "granularity",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.ReportOverviewResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/t/{tenantCode}/v1/creator/settings": {
|
"/t/{tenantCode}/v1/creator/settings": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Get channel settings",
|
"description": "Get channel settings",
|
||||||
@@ -4442,6 +4519,118 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.ReportExportForm": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"end_at": {
|
||||||
|
"description": "EndAt 统计结束时间(RFC3339,可选;默认当前时间)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"description": "Format 导出格式(仅支持 csv)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"granularity": {
|
||||||
|
"description": "Granularity 统计粒度(day;目前仅支持 day)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"start_at": {
|
||||||
|
"description": "StartAt 统计开始时间(RFC3339,可选;默认当前时间往前 7 天)。",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.ReportExportResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"description": "Content 导出内容(CSV 文本)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"filename": {
|
||||||
|
"description": "Filename 导出文件名。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mime_type": {
|
||||||
|
"description": "MimeType 导出内容类型。",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.ReportOverviewItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"date": {
|
||||||
|
"description": "Date 日期(YYYY-MM-DD)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"paid_amount": {
|
||||||
|
"description": "PaidAmount 当日已支付金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"paid_orders": {
|
||||||
|
"description": "PaidOrders 当日已支付订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"refund_amount": {
|
||||||
|
"description": "RefundAmount 当日退款金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"refund_orders": {
|
||||||
|
"description": "RefundOrders 当日退款订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.ReportOverviewResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"description": "Items 按日期拆分的趋势数据。",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/dto.ReportOverviewItem"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"description": "Summary 汇总指标。",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/dto.ReportSummary"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.ReportSummary": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"conversion_rate": {
|
||||||
|
"description": "ConversionRate 转化率(已支付订单数 / 累计曝光)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"paid_amount": {
|
||||||
|
"description": "PaidAmount 统计区间内已支付金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"paid_orders": {
|
||||||
|
"description": "PaidOrders 统计区间内已支付订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"refund_amount": {
|
||||||
|
"description": "RefundAmount 统计区间内退款金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"refund_orders": {
|
||||||
|
"description": "RefundOrders 统计区间内退款订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_views": {
|
||||||
|
"description": "TotalViews 内容累计曝光(全量累计值,用于粗略换算)。",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.Settings": {
|
"dto.Settings": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -1878,6 +1878,83 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/t/{tenantCode}/v1/creator/reports/export": {
|
||||||
|
"post": {
|
||||||
|
"description": "Export creator report overview",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"CreatorCenter"
|
||||||
|
],
|
||||||
|
"summary": "Export report overview",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Export form",
|
||||||
|
"name": "form",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.ReportExportForm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.ReportExportResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/t/{tenantCode}/v1/creator/reports/overview": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get creator report overview",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"CreatorCenter"
|
||||||
|
],
|
||||||
|
"summary": "Report overview",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Start time (RFC3339)",
|
||||||
|
"name": "start_at",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "End time (RFC3339)",
|
||||||
|
"name": "end_at",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Granularity (day)",
|
||||||
|
"name": "granularity",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.ReportOverviewResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/t/{tenantCode}/v1/creator/settings": {
|
"/t/{tenantCode}/v1/creator/settings": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Get channel settings",
|
"description": "Get channel settings",
|
||||||
@@ -4436,6 +4513,118 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.ReportExportForm": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"end_at": {
|
||||||
|
"description": "EndAt 统计结束时间(RFC3339,可选;默认当前时间)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"description": "Format 导出格式(仅支持 csv)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"granularity": {
|
||||||
|
"description": "Granularity 统计粒度(day;目前仅支持 day)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"start_at": {
|
||||||
|
"description": "StartAt 统计开始时间(RFC3339,可选;默认当前时间往前 7 天)。",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.ReportExportResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"description": "Content 导出内容(CSV 文本)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"filename": {
|
||||||
|
"description": "Filename 导出文件名。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mime_type": {
|
||||||
|
"description": "MimeType 导出内容类型。",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.ReportOverviewItem": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"date": {
|
||||||
|
"description": "Date 日期(YYYY-MM-DD)。",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"paid_amount": {
|
||||||
|
"description": "PaidAmount 当日已支付金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"paid_orders": {
|
||||||
|
"description": "PaidOrders 当日已支付订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"refund_amount": {
|
||||||
|
"description": "RefundAmount 当日退款金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"refund_orders": {
|
||||||
|
"description": "RefundOrders 当日退款订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.ReportOverviewResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"description": "Items 按日期拆分的趋势数据。",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/dto.ReportOverviewItem"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"description": "Summary 汇总指标。",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/dto.ReportSummary"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.ReportSummary": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"conversion_rate": {
|
||||||
|
"description": "ConversionRate 转化率(已支付订单数 / 累计曝光)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"paid_amount": {
|
||||||
|
"description": "PaidAmount 统计区间内已支付金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"paid_orders": {
|
||||||
|
"description": "PaidOrders 统计区间内已支付订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"refund_amount": {
|
||||||
|
"description": "RefundAmount 统计区间内退款金额(单位元)。",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"refund_orders": {
|
||||||
|
"description": "RefundOrders 统计区间内退款订单数。",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_views": {
|
||||||
|
"description": "TotalViews 内容累计曝光(全量累计值,用于粗略换算)。",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.Settings": {
|
"dto.Settings": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -768,6 +768,84 @@ definitions:
|
|||||||
description: Reason 退款原因/备注。
|
description: Reason 退款原因/备注。
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
dto.ReportExportForm:
|
||||||
|
properties:
|
||||||
|
end_at:
|
||||||
|
description: EndAt 统计结束时间(RFC3339,可选;默认当前时间)。
|
||||||
|
type: string
|
||||||
|
format:
|
||||||
|
description: Format 导出格式(仅支持 csv)。
|
||||||
|
type: string
|
||||||
|
granularity:
|
||||||
|
description: Granularity 统计粒度(day;目前仅支持 day)。
|
||||||
|
type: string
|
||||||
|
start_at:
|
||||||
|
description: StartAt 统计开始时间(RFC3339,可选;默认当前时间往前 7 天)。
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
dto.ReportExportResponse:
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
description: Content 导出内容(CSV 文本)。
|
||||||
|
type: string
|
||||||
|
filename:
|
||||||
|
description: Filename 导出文件名。
|
||||||
|
type: string
|
||||||
|
mime_type:
|
||||||
|
description: MimeType 导出内容类型。
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
dto.ReportOverviewItem:
|
||||||
|
properties:
|
||||||
|
date:
|
||||||
|
description: Date 日期(YYYY-MM-DD)。
|
||||||
|
type: string
|
||||||
|
paid_amount:
|
||||||
|
description: PaidAmount 当日已支付金额(单位元)。
|
||||||
|
type: number
|
||||||
|
paid_orders:
|
||||||
|
description: PaidOrders 当日已支付订单数。
|
||||||
|
type: integer
|
||||||
|
refund_amount:
|
||||||
|
description: RefundAmount 当日退款金额(单位元)。
|
||||||
|
type: number
|
||||||
|
refund_orders:
|
||||||
|
description: RefundOrders 当日退款订单数。
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
dto.ReportOverviewResponse:
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
description: Items 按日期拆分的趋势数据。
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/dto.ReportOverviewItem'
|
||||||
|
type: array
|
||||||
|
summary:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/dto.ReportSummary'
|
||||||
|
description: Summary 汇总指标。
|
||||||
|
type: object
|
||||||
|
dto.ReportSummary:
|
||||||
|
properties:
|
||||||
|
conversion_rate:
|
||||||
|
description: ConversionRate 转化率(已支付订单数 / 累计曝光)。
|
||||||
|
type: number
|
||||||
|
paid_amount:
|
||||||
|
description: PaidAmount 统计区间内已支付金额(单位元)。
|
||||||
|
type: number
|
||||||
|
paid_orders:
|
||||||
|
description: PaidOrders 统计区间内已支付订单数。
|
||||||
|
type: integer
|
||||||
|
refund_amount:
|
||||||
|
description: RefundAmount 统计区间内退款金额(单位元)。
|
||||||
|
type: number
|
||||||
|
refund_orders:
|
||||||
|
description: RefundOrders 统计区间内退款订单数。
|
||||||
|
type: integer
|
||||||
|
total_views:
|
||||||
|
description: TotalViews 内容累计曝光(全量累计值,用于粗略换算)。
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
dto.Settings:
|
dto.Settings:
|
||||||
properties:
|
properties:
|
||||||
avatar:
|
avatar:
|
||||||
@@ -2885,6 +2963,56 @@ paths:
|
|||||||
summary: Add payout account
|
summary: Add payout account
|
||||||
tags:
|
tags:
|
||||||
- CreatorCenter
|
- CreatorCenter
|
||||||
|
/t/{tenantCode}/v1/creator/reports/export:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Export creator report overview
|
||||||
|
parameters:
|
||||||
|
- description: Export form
|
||||||
|
in: body
|
||||||
|
name: form
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.ReportExportForm'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.ReportExportResponse'
|
||||||
|
summary: Export report overview
|
||||||
|
tags:
|
||||||
|
- CreatorCenter
|
||||||
|
/t/{tenantCode}/v1/creator/reports/overview:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get creator report overview
|
||||||
|
parameters:
|
||||||
|
- description: Start time (RFC3339)
|
||||||
|
in: query
|
||||||
|
name: start_at
|
||||||
|
type: string
|
||||||
|
- description: End time (RFC3339)
|
||||||
|
in: query
|
||||||
|
name: end_at
|
||||||
|
type: string
|
||||||
|
- description: Granularity (day)
|
||||||
|
in: query
|
||||||
|
name: granularity
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.ReportOverviewResponse'
|
||||||
|
summary: Report overview
|
||||||
|
tags:
|
||||||
|
- CreatorCenter
|
||||||
/t/{tenantCode}/v1/creator/settings:
|
/t/{tenantCode}/v1/creator/settings:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
@@ -196,6 +196,7 @@
|
|||||||
- ID 类型已统一为 int64(仅保留 upload_id/external_id/uuid 等非数字标识)。
|
- ID 类型已统一为 int64(仅保留 upload_id/external_id/uuid 等非数字标识)。
|
||||||
- 内容资源权限与预览差异化(未购预览、已购/管理员/成员全量)。
|
- 内容资源权限与预览差异化(未购预览、已购/管理员/成员全量)。
|
||||||
- 审计操作显式传入操作者信息(服务层不再依赖 ctx 读取)。
|
- 审计操作显式传入操作者信息(服务层不再依赖 ctx 读取)。
|
||||||
|
- 运营统计报表(overview + CSV 导出基础版)。
|
||||||
|
|
||||||
## 里程碑建议
|
## 里程碑建议
|
||||||
- M1:完成 P0
|
- M1:完成 P0
|
||||||
|
|||||||
Reference in New Issue
Block a user