feat: add creator report overview export

This commit is contained in:
2026-01-13 11:44:29 +08:00
parent 4e35dff05c
commit 19b15bf20a
9 changed files with 1010 additions and 0 deletions

View File

@@ -69,6 +69,50 @@ func (c *Creator) CreateMemberInvite(
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
//
// @Router /t/:tenantCode/v1/creator/dashboard [get]

View 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"`
}

View File

@@ -191,6 +191,12 @@ func (r *Routes) Register(router fiber.Router) {
r.creator.ListPayoutAccounts,
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")
router.Get("/t/:tenantCode/v1/creator/settings"[len(r.Path()):], DataFunc1(
r.creator.GetSettings,
@@ -234,6 +240,12 @@ func (r *Routes) Register(router fiber.Router) {
Local[*models.User]("__ctx_user"),
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")
router.Post("/t/:tenantCode/v1/creator/withdraw"[len(r.Path()):], Func2(
r.creator.Withdraw,

View 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)
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"testing"
"time"
"quyun/v2/app/commands/testx"
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")
})
}

View File

@@ -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": {
"get": {
"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": {
"type": "object",
"properties": {

View File

@@ -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": {
"get": {
"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": {
"type": "object",
"properties": {

View File

@@ -768,6 +768,84 @@ definitions:
description: Reason 退款原因/备注。
type: string
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:
properties:
avatar:
@@ -2885,6 +2963,56 @@ paths:
summary: Add payout account
tags:
- 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:
get:
consumes:

View File

@@ -196,6 +196,7 @@
- ID 类型已统一为 int64仅保留 upload_id/external_id/uuid 等非数字标识)。
- 内容资源权限与预览差异化(未购预览、已购/管理员/成员全量)。
- 审计操作显式传入操作者信息(服务层不再依赖 ctx 读取)。
- 运营统计报表overview + CSV 导出基础版)。
## 里程碑建议
- M1完成 P0