667 lines
30 KiB
Go
667 lines
30 KiB
Go
package report
|
||
|
||
// ============================================================
|
||
// 通用报表引擎 - 完整调用示例
|
||
// ============================================================
|
||
//
|
||
// 包名: dataengine/common/report
|
||
// 入口: report.GetService() → *ReportService
|
||
//
|
||
// 接口一览:
|
||
// ┌──────────────┬─────────────────────────────────────────────────┐
|
||
// │ 分类 │ 接口 │
|
||
// ├──────────────┼─────────────────────────────────────────────────┤
|
||
// │ 配置 CRUD │ SaveBusiness / DelBusiness / GetBusiness │
|
||
// │ │ SaveReport / DelReport / GetReport │
|
||
// │ │ SaveField / DelField / GetField │
|
||
// │ │ SaveExtractConfig / DelExtractConfig / Get.. │
|
||
// ├──────────────┼─────────────────────────────────────────────────┤
|
||
// │ 数据抽取 │ ExtractDailyData / AutoCreateStatTable │
|
||
// ├──────────────┼─────────────────────────────────────────────────┤
|
||
// │ 报表查询 │ QueryReportByUserSelect │
|
||
// ├──────────────┼─────────────────────────────────────────────────┤
|
||
// │ 辅助查询 │ GetAllBusinesses / GetAllReports │
|
||
// │ │ GetReportFields / GetExtractConfigs │
|
||
// └──────────────┴─────────────────────────────────────────────────┘
|
||
//
|
||
// ============================================================
|
||
|
||
// ============================================================================
|
||
// 场景一:新平台零代码接入(快手电商为例)
|
||
// ============================================================================
|
||
//
|
||
// 背景:快手订单数据已通过数据引擎同步到 kuaishou_order_list 表(每条订单一行)。
|
||
// 需求:按店铺+天聚合订单数据 → 自动建统计宽表 → 抽取 → 前端自由查询报表。
|
||
//
|
||
// 全程只调 API 不写 SQL,前端管理后台可直接操作。
|
||
|
||
/*
|
||
package example
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"time"
|
||
|
||
"dataengine/common/report"
|
||
"dataengine/common/report/model"
|
||
)
|
||
|
||
func KuaishouExample() {
|
||
ctx := context.Background()
|
||
svc := report.GetService()
|
||
|
||
// ─── Step 1: 注册业务 ───────────────────────────────────────────
|
||
// 一个业务就是一个数据源平台(快手/抖音/淘宝/...)
|
||
_, _ = svc.SaveBusiness(ctx, &model.SaveBusinessReq{
|
||
BusinessCode: "KUAISHOU", // 唯一标识,后续所有接口都用它
|
||
BusinessName: "快手电商",
|
||
Description: "快手平台电商业务线",
|
||
Status: model.StatusActive,
|
||
Operator: "admin",
|
||
})
|
||
|
||
// ─── Step 2: 注册报表 ───────────────────────────────────────────
|
||
// 一个业务可以有多个报表(店铺日报、商品日报、主播日报...)
|
||
_, _ = svc.SaveReport(ctx, &model.SaveReportReq{
|
||
BusinessCode: "KUAISHOU",
|
||
ReportCode: "shop_daily_report", // 报表唯一编码
|
||
ReportName: "快手店铺日报",
|
||
StatTableName: "stat_kuaishou_shop_daily", // 统计宽表名(自动创建)
|
||
DateField: "stat_date", // 日期字段
|
||
ConflictKeys: []string{"shop_id", "stat_date"}, // 唯一约束(upsert 依据)
|
||
Operator: "admin",
|
||
})
|
||
|
||
// ─── Step 3: 配置字段(前端可选择的所有维度/指标/筛选) ─────────
|
||
// 这是前端"自定义报表"的数据源 —— 前端建好字段后,
|
||
// 用户选择哪些维度、哪些指标、怎么筛选,实时查。
|
||
|
||
// 3a. 维度字段:分组依据,不可聚合
|
||
dimensions := []model.SaveFieldReq{
|
||
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
|
||
FieldCode: "shop_id", FieldName: "店铺ID", FieldType: "STRING",
|
||
FieldRole: "DIMENSION", IsSortable: true, SortOrder: 1, GroupName: "店铺", Operator: "admin"},
|
||
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
|
||
FieldCode: "shop_name", FieldName: "店铺名称", FieldType: "STRING",
|
||
FieldRole: "DIMENSION", IsSortable: true, SortOrder: 2, GroupName: "店铺", Operator: "admin"},
|
||
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
|
||
FieldCode: "stat_date", FieldName: "统计日期", FieldType: "DATE",
|
||
FieldRole: "DIMENSION", IsSortable: true, SortOrder: 3, GroupName: "时间", Operator: "admin"},
|
||
}
|
||
|
||
// 3b. 指标字段:聚合度量,前端可选 SUM/COUNT/AVG/MAX/MIN
|
||
indicators := []model.SaveFieldReq{
|
||
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
|
||
FieldCode: "order_count", FieldName: "订单数", FieldType: "INT",
|
||
FieldRole: "INDICATOR", IsAggregatable: true, DefaultAggregate: "SUM",
|
||
ValidAggregates: []string{"SUM", "COUNT", "AVG", "MAX", "MIN"},
|
||
SortOrder: 10, GroupName: "订单", Operator: "admin"},
|
||
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
|
||
FieldCode: "order_amount", FieldName: "订单金额(元)", FieldType: "FLOAT",
|
||
FieldRole: "INDICATOR", IsAggregatable: true, DefaultAggregate: "SUM",
|
||
ValidAggregates: []string{"SUM", "AVG", "MAX", "MIN"},
|
||
SortOrder: 11, GroupName: "金额", Unit: "元", Operator: "admin"},
|
||
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
|
||
FieldCode: "paid_amount", FieldName: "实付金额(元)", FieldType: "FLOAT",
|
||
FieldRole: "INDICATOR", IsAggregatable: true, DefaultAggregate: "SUM",
|
||
ValidAggregates: []string{"SUM", "AVG"},
|
||
SortOrder: 12, GroupName: "金额", Unit: "元", Operator: "admin"},
|
||
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
|
||
FieldCode: "refund_amount", FieldName: "退款金额(元)", FieldType: "FLOAT",
|
||
FieldRole: "INDICATOR", IsAggregatable: true, DefaultAggregate: "SUM",
|
||
ValidAggregates: []string{"SUM", "AVG"},
|
||
SortOrder: 13, GroupName: "退款", Unit: "元", Operator: "admin"},
|
||
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
|
||
FieldCode: "buyer_count", FieldName: "下单买家数", FieldType: "INT",
|
||
FieldRole: "INDICATOR", IsAggregatable: true, DefaultAggregate: "COUNT",
|
||
ValidAggregates: []string{"COUNT"},
|
||
SortOrder: 14, GroupName: "用户", Operator: "admin"},
|
||
// 衍生指标:退款率 = 退款金额 / 订单金额 * 100
|
||
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
|
||
FieldCode: "refund_rate", FieldName: "退款率", FieldType: "FLOAT",
|
||
FieldRole: "INDICATOR", IsAggregatable: true, DefaultAggregate: "AVG",
|
||
Expression: "{refund_amount} / NULLIF({order_amount}, 0) * 100",
|
||
ExpressionType: "CALCULATED", FormatPattern: "#,##0.00",
|
||
Unit: "%", SortOrder: 20, GroupName: "退款", Operator: "admin"},
|
||
}
|
||
|
||
// 3c. 筛选字段:纯筛选,不出现在 SELECT 中
|
||
filters := []model.SaveFieldReq{
|
||
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
|
||
FieldCode: "order_status", FieldName: "订单状态", FieldType: "STRING",
|
||
FieldRole: "FILTER", IsFilterable: true,
|
||
FilterOperators: []string{"=", "IN"},
|
||
SortOrder: 30, GroupName: "筛选", Operator: "admin"},
|
||
}
|
||
|
||
for _, f := range dimensions {
|
||
_, _ = svc.SaveField(ctx, &f)
|
||
}
|
||
for _, f := range indicators {
|
||
_, _ = svc.SaveField(ctx, &f)
|
||
}
|
||
for _, f := range filters {
|
||
_, _ = svc.SaveField(ctx, &f)
|
||
}
|
||
|
||
// ─── Step 4: 配置数据抽取规则 ────────────────────────────────────
|
||
// 关键:AGGREGATE 模式,从源表聚合到统计宽表
|
||
_, _ = svc.SaveExtractConfig(ctx, &model.SaveExtractConfigReq{
|
||
BusinessCode: "KUAISHOU",
|
||
ReportCode: "shop_daily_report",
|
||
ExtractCode: "extract_shop_daily",
|
||
ExtractName: "快手店铺订单按天聚合",
|
||
SourceTableName: "kuaishou_order_list", // ← 源表(订单明细)
|
||
SourceTableAlias: "o",
|
||
TargetTableName: "stat_kuaishou_shop_daily", // ← 目标(统计宽表)
|
||
ExtractType: model.ExtractTypeIncremental, // 增量抽取
|
||
ExtractMode: model.ExtractModeAggregate, // ← 聚合模式
|
||
ExtractKeyField: "created_at", // 增量依据字段
|
||
GroupByFields: []string{"shop_id"}, // ← GROUP BY
|
||
FilterExpression: "o.order_status != 'CANCELLED'", // 过滤取消订单
|
||
// 字段映射:源表字段 → 目标表字段 + 聚合函数
|
||
FieldMappings: []model.FieldMapping{
|
||
{SourceField: "shop_id", TargetField: "shop_id", FieldType: "STRING"},
|
||
{SourceField: "shop_name", TargetField: "shop_name", FieldType: "STRING"},
|
||
{SourceField: "id", TargetField: "order_count", FieldType: "INT", AggregateFunction: "COUNT"},
|
||
{SourceField: "order_amount", TargetField: "order_amount", FieldType: "FLOAT", AggregateFunction: "SUM"},
|
||
{SourceField: "paid_amount", TargetField: "paid_amount", FieldType: "FLOAT", AggregateFunction: "SUM"},
|
||
{SourceField: "refund_amount",TargetField: "refund_amount",FieldType: "FLOAT", AggregateFunction: "SUM"},
|
||
{SourceField: "buyer_id", TargetField: "buyer_count", FieldType: "INT", AggregateFunction: "COUNT"},
|
||
},
|
||
BatchSize: 1000,
|
||
Operator: "admin",
|
||
})
|
||
|
||
// ─── Step 5: 每天定时抽取(cron/k8s CronJob 中调用) ─────────
|
||
// ExtractDailyData 内部:
|
||
// 1. 检测 stat_kuaishou_shop_daily 表是否存在
|
||
// 2. 不存在 → 根据 FieldConfig 自动 CREATE TABLE
|
||
// 3. 从 kuaishou_order_list 按 AGGREGATE 模式抽取当天数据
|
||
// 4. UPSERT 到统计宽表
|
||
today := time.Now().Format("2006-01-02")
|
||
resp, _ := svc.ExtractDailyData(ctx, "KUAISHOU", "shop_daily_report", today, "cron")
|
||
fmt.Printf("[%s] 抽取完成: 总%d 成功%d 失败%d 耗时%dms\n",
|
||
today, resp.TotalCount, resp.SuccessCount, resp.FailCount, resp.ExecTimeMs)
|
||
|
||
// 实际抽取生成的 SQL(AGGREGATE 模式):
|
||
//
|
||
// INSERT INTO stat_kuaishou_shop_daily (...)
|
||
// SELECT ROW_NUMBER() OVER () AS id,
|
||
// '2026-06-10' AS stat_date,
|
||
// o.shop_id, o.shop_name,
|
||
// COUNT(o.id) AS order_count,
|
||
// SUM(o.order_amount) AS order_amount,
|
||
// SUM(o.paid_amount) AS paid_amount,
|
||
// SUM(o.refund_amount) AS refund_amount,
|
||
// COUNT(o.buyer_id) AS buyer_count
|
||
// FROM kuaishou_order_list o
|
||
// WHERE o.created_at::date = '2026-06-10'
|
||
// GROUP BY o.shop_id
|
||
|
||
// ─── Step 6: 前端查询 ──────────────────────────────────────────
|
||
|
||
// 6a. 前端先拉取可用字段列表
|
||
fields, _ := svc.GetReportFields(ctx, "KUAISHOU", "shop_daily_report")
|
||
// 返回:
|
||
// dimensions: [shop_id, shop_name, stat_date]
|
||
// indicators: [order_count, order_amount, paid_amount, refund_amount, buyer_count, refund_rate]
|
||
// filters: [order_status]
|
||
// 前端据此渲染选择器面板
|
||
|
||
// 6b. 用户选择: 维度=店铺, 指标=金额+订单数, 时间=近7天, 排名=TOP10
|
||
top10Req := &model.UserSelectQueryReq{
|
||
BusinessCode: "KUAISHOU",
|
||
ReportCode: "shop_daily_report",
|
||
Dimensions: []string{"shop_id", "shop_name"},
|
||
Indicators: []model.IndicatorSelect{
|
||
{FieldCode: "order_amount", Aggregate: "SUM", Alias: "total_amount"},
|
||
{FieldCode: "order_count", Aggregate: "SUM", Alias: "total_orders"},
|
||
{FieldCode: "paid_amount", Aggregate: "SUM", Alias: "total_paid"},
|
||
{FieldCode: "refund_rate", Aggregate: "AVG", Alias: "avg_refund_rate"},
|
||
},
|
||
TimeRange: &model.TimeRange{
|
||
StartDate: time.Now().AddDate(0, 0, -7).Format("2006-01-02"),
|
||
EndDate: today,
|
||
},
|
||
OrderBy: []model.OrderCondition{{FieldCode: "total_amount", Direction: "DESC"}},
|
||
Page: 1,
|
||
PageSize: 10,
|
||
}
|
||
top10Resp, _ := svc.QueryReportByUserSelect(ctx, top10Req)
|
||
fmt.Printf("TOP10 销售额排行 (总数=%d):\n", top10Resp.Total)
|
||
for i, row := range top10Resp.List {
|
||
b, _ := json.Marshal(row)
|
||
fmt.Printf(" %d. %s\n", i+1, string(b))
|
||
}
|
||
// 实际生成 SQL:
|
||
// SELECT o.shop_id, o.shop_name,
|
||
// SUM(o.order_amount) AS total_amount,
|
||
// SUM(o.order_count) AS total_orders,
|
||
// SUM(o.paid_amount) AS total_paid,
|
||
// AVG(refund_amount / NULLIF(order_amount,0) * 100) AS avg_refund_rate
|
||
// FROM stat_kuaishou_shop_daily o
|
||
// WHERE stat_date BETWEEN '2026-06-03' AND '2026-06-10'
|
||
// GROUP BY o.shop_id, o.shop_name
|
||
// ORDER BY total_amount DESC
|
||
// LIMIT 10 OFFSET 0
|
||
|
||
// 6c. 用户切换维度: 每日趋势(聚合所有店铺)
|
||
trendReq := &model.UserSelectQueryReq{
|
||
BusinessCode: "KUAISHOU",
|
||
ReportCode: "shop_daily_report",
|
||
Dimensions: []string{"stat_date"},
|
||
Indicators: []model.IndicatorSelect{
|
||
{FieldCode: "order_amount", Aggregate: "SUM", Alias: "daily_amount"},
|
||
{FieldCode: "order_count", Aggregate: "SUM", Alias: "daily_orders"},
|
||
{FieldCode: "refund_amount",Aggregate: "SUM", Alias: "daily_refund"},
|
||
},
|
||
TimeRange: &model.TimeRange{
|
||
StartDate: time.Now().AddDate(0, 0, -30).Format("2006-01-02"),
|
||
EndDate: today,
|
||
},
|
||
TimeGroup: "day",
|
||
OrderBy: []model.OrderCondition{{FieldCode: "stat_date", Direction: "ASC"}},
|
||
PageSize: 100,
|
||
}
|
||
trendResp, _ := svc.QueryReportByUserSelect(ctx, trendReq)
|
||
fmt.Printf("30天趋势 (共%d行):\n", trendResp.Total)
|
||
|
||
// 6d. 加筛选条件: 只看活跃订单,金额>10000
|
||
filteredReq := &model.UserSelectQueryReq{
|
||
BusinessCode: "KUAISHOU",
|
||
ReportCode: "shop_daily_report",
|
||
Dimensions: []string{"shop_id", "shop_name"},
|
||
Indicators: []model.IndicatorSelect{
|
||
{FieldCode: "order_amount", Aggregate: "SUM", Alias: "total_amount"},
|
||
},
|
||
Filters: []model.FilterCondition{
|
||
{FieldCode: "order_status", Operator: "=", Value: "ACTIVE"},
|
||
{FieldCode: "total_amount", Operator: ">=", Value: 10000}, // 指标别名也可筛选
|
||
},
|
||
OrderBy: []model.OrderCondition{{FieldCode: "total_amount", Direction: "DESC"}},
|
||
PageSize: 20,
|
||
}
|
||
_ = filteredReq
|
||
|
||
// 6e. 按周汇总趋势
|
||
weeklyReq := &model.UserSelectQueryReq{
|
||
BusinessCode: "KUAISHOU",
|
||
ReportCode: "shop_daily_report",
|
||
Dimensions: []string{"shop_id"},
|
||
Indicators: []model.IndicatorSelect{
|
||
{FieldCode: "order_amount", Aggregate: "SUM", Alias: "weekly_amount"},
|
||
},
|
||
TimeGroup: "week",
|
||
OrderBy: []model.OrderCondition{{FieldCode: "weekly_amount", Direction: "DESC"}},
|
||
PageSize: 50,
|
||
}
|
||
_ = weeklyReq
|
||
}
|
||
|
||
|
||
// ============================================================================
|
||
// 场景二:已有配置管理(CRUD 二次开发参考)
|
||
// ============================================================================
|
||
|
||
func CRUDExample() {
|
||
ctx := context.Background()
|
||
svc := report.GetService()
|
||
|
||
// ── 业务 CRUD ────────────────────────────────────────────
|
||
|
||
// 新增
|
||
result, _ := svc.SaveBusiness(ctx, &model.SaveBusinessReq{
|
||
BusinessCode: "DOUYIN", BusinessName: "抖音电商",
|
||
Operator: "admin",
|
||
})
|
||
businessId := result.ID
|
||
|
||
// 修改(传 ID 即修改)
|
||
result, _ = svc.SaveBusiness(ctx, &model.SaveBusinessReq{
|
||
ID: &businessId, BusinessCode: "DOUYIN",
|
||
BusinessName: "抖音电商(新版)", Operator: "admin",
|
||
})
|
||
|
||
// 查询
|
||
biz, _ := svc.GetBusiness(ctx, businessId)
|
||
fmt.Printf("%s: %s\n", biz.BusinessCode, biz.BusinessName)
|
||
|
||
// 删除
|
||
svc.DeleteBusiness(ctx, businessId)
|
||
|
||
// 全部列表
|
||
allBiz, _ := svc.GetAllBusinesses(ctx)
|
||
for _, b := range allBiz {
|
||
fmt.Printf("- %s (%s)\n", b.BusinessCode, b.BusinessName)
|
||
}
|
||
|
||
// ── 报表 CRUD ────────────────────────────────────────────
|
||
|
||
reportResult, _ := svc.SaveReport(ctx, &model.SaveReportReq{
|
||
BusinessCode: "DOUYIN",
|
||
ReportCode: "shop_daily_report",
|
||
ReportName: "抖音店铺日报",
|
||
StatTableName: "stat_douyin_shop_daily",
|
||
ConflictKeys: []string{"shop_id", "stat_date"},
|
||
Operator: "admin",
|
||
})
|
||
reportId := reportResult.ID
|
||
|
||
rpt, _ := svc.GetReport(ctx, reportId)
|
||
svc.DeleteReport(ctx, reportId)
|
||
_ = rpt
|
||
|
||
// ── 字段 CRUD ────────────────────────────────────────────
|
||
|
||
// 新增字段(id 不传 = 新增)
|
||
fieldResult, _ := svc.SaveField(ctx, &model.SaveFieldReq{
|
||
BusinessCode: "DOUYIN", ReportCode: "shop_daily_report",
|
||
FieldCode: "order_amount", FieldName: "订单金额",
|
||
FieldType: "FLOAT", FieldRole: "INDICATOR",
|
||
IsAggregatable: true, DefaultAggregate: "SUM",
|
||
ValidAggregates: []string{"SUM", "AVG", "MAX", "MIN"},
|
||
SortOrder: 10, GroupName: "金额", Operator: "admin",
|
||
})
|
||
fieldId := fieldResult.ID
|
||
|
||
// 修改字段(传 id = 更新)
|
||
svc.SaveField(ctx, &model.SaveFieldReq{
|
||
ID: &fieldId,
|
||
FieldName: "订单金额(元)",
|
||
Operator: "admin",
|
||
// ... 只传要修改的字段,未传的保持原值
|
||
})
|
||
|
||
f, _ := svc.GetField(ctx, fieldId)
|
||
svc.DeleteField(ctx, fieldId)
|
||
_ = f
|
||
|
||
// ── 抽取配置 CRUD ────────────────────────────────────────
|
||
|
||
ecResult, _ := svc.SaveExtractConfig(ctx, &model.SaveExtractConfigReq{
|
||
BusinessCode: "DOUYIN", ReportCode: "shop_daily_report",
|
||
ExtractCode: "extract_daily", ExtractName: "按天聚合抽取",
|
||
SourceTableName: "douyin_order_list", SourceTableAlias: "o",
|
||
TargetTableName: "stat_douyin_shop_daily",
|
||
ExtractMode: "AGGREGATE",
|
||
ExtractKeyField: "created_at",
|
||
GroupByFields: []string{"shop_id"},
|
||
FieldMappings: []model.FieldMapping{
|
||
{SourceField: "id", TargetField: "order_count", FieldType: "INT", AggregateFunction: "COUNT"},
|
||
{SourceField: "order_amount", TargetField: "order_amount", FieldType: "FLOAT", AggregateFunction: "SUM"},
|
||
},
|
||
Operator: "admin",
|
||
})
|
||
ecId := ecResult.ID
|
||
|
||
ec, _ := svc.GetExtractConfig(ctx, ecId)
|
||
allEc, _ := svc.GetExtractConfigs(ctx, "DOUYIN", "shop_daily_report")
|
||
svc.DeleteExtractConfig(ctx, ecId)
|
||
_, _ = ec, allEc
|
||
}
|
||
|
||
|
||
// ============================================================================
|
||
// 场景三:任意平台接入的通用模式(零硬编码)
|
||
// ============================================================================
|
||
//
|
||
// 假设要接入淘宝平台,taobao_order_list 表已有数据。
|
||
// 全程不写任何代码,只需调 CRUD API。
|
||
|
||
func GenericPlatformExample() {
|
||
ctx := context.Background()
|
||
svc := report.GetService()
|
||
|
||
// 1. 前端注册业务
|
||
svc.SaveBusiness(ctx, &model.SaveBusinessReq{
|
||
BusinessCode: "TAOBAO", BusinessName: "淘宝电商",
|
||
Operator: "admin",
|
||
})
|
||
|
||
// 2. 注册报表 + 指定统计宽表名(以后表名不再手工管理)
|
||
svc.SaveReport(ctx, &model.SaveReportReq{
|
||
BusinessCode: "TAOBAO",
|
||
ReportCode: "shop_daily_report",
|
||
ReportName: "淘宝店铺日报",
|
||
StatTableName: "stat_taobao_shop_daily",
|
||
ConflictKeys: []string{"shop_id", "stat_date"},
|
||
Operator: "admin",
|
||
})
|
||
|
||
// 3. 前端选择统计维度(自由组合,随时增删改)
|
||
svc.SaveField(ctx, &model.SaveFieldReq{
|
||
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
|
||
FieldCode: "shop_id", FieldName: "店铺ID",
|
||
FieldType: "STRING", FieldRole: "DIMENSION",
|
||
SortOrder: 1, GroupName: "店铺", Operator: "admin",
|
||
})
|
||
svc.SaveField(ctx, &model.SaveFieldReq{
|
||
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
|
||
FieldCode: "shop_name", FieldName: "店铺名称",
|
||
FieldType: "STRING", FieldRole: "DIMENSION",
|
||
SortOrder: 2, GroupName: "店铺", Operator: "admin",
|
||
})
|
||
svc.SaveField(ctx, &model.SaveFieldReq{
|
||
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
|
||
FieldCode: "stat_date", FieldName: "统计日期",
|
||
FieldType: "DATE", FieldRole: "DIMENSION",
|
||
SortOrder: 3, GroupName: "时间", Operator: "admin",
|
||
})
|
||
|
||
// 4. 前端选择统计指标(自由组合,随时增删改)
|
||
svc.SaveField(ctx, &model.SaveFieldReq{
|
||
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
|
||
FieldCode: "order_count", FieldName: "订单数",
|
||
FieldType: "INT", FieldRole: "INDICATOR",
|
||
IsAggregatable: true, DefaultAggregate: "SUM",
|
||
ValidAggregates: []string{"SUM", "COUNT", "AVG"},
|
||
SortOrder: 10, GroupName: "订单", Operator: "admin",
|
||
})
|
||
svc.SaveField(ctx, &model.SaveFieldReq{
|
||
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
|
||
FieldCode: "order_amount", FieldName: "订单金额",
|
||
FieldType: "FLOAT", FieldRole: "INDICATOR",
|
||
IsAggregatable: true, DefaultAggregate: "SUM",
|
||
ValidAggregates: []string{"SUM", "AVG", "MAX", "MIN"},
|
||
SortOrder: 11, GroupName: "金额", Unit: "元", Operator: "admin",
|
||
})
|
||
svc.SaveField(ctx, &model.SaveFieldReq{
|
||
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
|
||
FieldCode: "buyer_count", FieldName: "下单买家数",
|
||
FieldType: "INT", FieldRole: "INDICATOR",
|
||
IsAggregatable: true, DefaultAggregate: "COUNT",
|
||
ValidAggregates: []string{"COUNT"},
|
||
SortOrder: 12, GroupName: "用户", Operator: "admin",
|
||
})
|
||
|
||
// 5. 配置抽取规则
|
||
svc.SaveExtractConfig(ctx, &model.SaveExtractConfigReq{
|
||
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
|
||
ExtractCode: "extract_daily", ExtractName: "淘宝按天聚合",
|
||
SourceTableName: "taobao_order_list", SourceTableAlias: "o",
|
||
TargetTableName: "stat_taobao_shop_daily",
|
||
ExtractMode: "AGGREGATE",
|
||
ExtractKeyField: "created_at",
|
||
GroupByFields: []string{"shop_id"},
|
||
FieldMappings: []model.FieldMapping{
|
||
{SourceField: "shop_id", TargetField: "shop_id", FieldType: "STRING"},
|
||
{SourceField: "shop_name", TargetField: "shop_name", FieldType: "STRING"},
|
||
{SourceField: "id", TargetField: "order_count", FieldType: "INT", AggregateFunction: "COUNT"},
|
||
{SourceField: "order_amount", TargetField: "order_amount", FieldType: "FLOAT", AggregateFunction: "SUM"},
|
||
{SourceField: "buyer_id", TargetField: "buyer_count", FieldType: "INT", AggregateFunction: "COUNT"},
|
||
},
|
||
BatchSize: 1000,
|
||
Operator: "admin",
|
||
})
|
||
|
||
// 6. 定时任务每天执行
|
||
svc.ExtractDailyData(ctx, "TAOBAO", "shop_daily_report", "2026-06-10", "cron")
|
||
|
||
// 7. 前端实时查询
|
||
req := &model.UserSelectQueryReq{
|
||
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
|
||
Dimensions: []string{"shop_id", "shop_name"},
|
||
Indicators: []model.IndicatorSelect{
|
||
{FieldCode: "order_amount", Aggregate: "SUM", Alias: "total"},
|
||
{FieldCode: "order_count", Aggregate: "SUM", Alias: "orders"},
|
||
},
|
||
TimeRange: &model.TimeRange{StartDate: "2026-06-01", EndDate: "2026-06-10"},
|
||
Page: 1, PageSize: 20,
|
||
}
|
||
resp, _ := svc.QueryReportByUserSelect(ctx, req)
|
||
fmt.Printf("查询结果: 共%d条, 耗时%dms\n", resp.Total, resp.ExecTimeMs)
|
||
|
||
// 平台接入完毕。全程零代码改动。
|
||
}
|
||
|
||
|
||
// ============================================================================
|
||
// 场景四:在外部业务服务(如 goview-report)中调用
|
||
// ============================================================================
|
||
|
||
func ExternalServiceExample() {
|
||
ctx := context.Background()
|
||
svc := report.GetService()
|
||
|
||
// 直接用,不需要初始化,内部自动懒加载和建表。
|
||
today := time.Now().Format("2006-01-02")
|
||
|
||
// 按天抽取
|
||
svc.ExtractDailyData(ctx, "KUAISHOU", "shop_daily_report", today, "cron")
|
||
|
||
// 实时查询
|
||
svc.QueryReportByUserSelect(ctx, &model.UserSelectQueryReq{
|
||
BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
|
||
Dimensions: []string{"shop_id", "shop_name"},
|
||
Indicators: []model.IndicatorSelect{
|
||
{FieldCode: "order_amount", Aggregate: "SUM", Alias: "total_amount"},
|
||
},
|
||
TimeRange: &model.TimeRange{StartDate: "2026-06-01", EndDate: today},
|
||
Page: 1, PageSize: 20,
|
||
})
|
||
}
|
||
|
||
|
||
// ============================================================================
|
||
// 场景五:Direct 模式(逐行抽取,不做聚合)
|
||
// ============================================================================
|
||
//
|
||
// 适用于源表已经是一行一条统计记录的场景(如已预聚合的报表源表)。
|
||
|
||
func DirectModeExample() {
|
||
ctx := context.Background()
|
||
svc := report.GetService()
|
||
|
||
svc.SaveBusiness(ctx, &model.SaveBusinessReq{
|
||
BusinessCode: "HDWL", BusinessName: "HDWL业务", Operator: "admin",
|
||
})
|
||
svc.SaveReport(ctx, &model.SaveReportReq{
|
||
BusinessCode: "HDWL", ReportCode: "shop_daily_stat",
|
||
ReportName: "HDWL店铺日报", StatTableName: "stat_hdwl_shop_daily",
|
||
ConflictKeys: []string{"report_date"},
|
||
Operator: "admin",
|
||
})
|
||
|
||
// Direct 模式:不聚合,逐行映射
|
||
svc.SaveExtractConfig(ctx, &model.SaveExtractConfigReq{
|
||
BusinessCode: "HDWL", ReportCode: "shop_daily_stat",
|
||
ExtractCode: "extract_direct", ExtractName: "直接逐行抽取",
|
||
SourceTableName: "hdwl_daily_summary", SourceTableAlias: "s",
|
||
TargetTableName: "stat_hdwl_shop_daily",
|
||
ExtractMode: "DIRECT", // ← DIRECT 模式
|
||
ExtractKeyField: "report_date",
|
||
FieldMappings: []model.FieldMapping{
|
||
{SourceField: "shop_id", TargetField: "shop_id", FieldType: "STRING"},
|
||
{SourceField: "shop_name", TargetField: "shop_name", FieldType: "STRING"},
|
||
{SourceField: "total_sale", TargetField: "sale_amount", FieldType: "FLOAT"},
|
||
{SourceField: "total_orders", TargetField: "order_count", FieldType: "INT"},
|
||
},
|
||
Operator: "admin",
|
||
})
|
||
|
||
svc.ExtractDailyData(ctx, "HDWL", "shop_daily_stat", "2026-06-10", "cron")
|
||
}
|
||
|
||
|
||
// ============================================================================
|
||
// 场景六:前端交互流程参考
|
||
// ============================================================================
|
||
//
|
||
// 前端代码调用示例(伪代码,展示 API 调用顺序):
|
||
//
|
||
// // 1. 页面加载 → 获取业务列表 → 渲染下拉框
|
||
// GET /api/report/businesses
|
||
// 返回: [{ businessCode: "KUAISHOU", businessName: "快手电商" }, ...]
|
||
//
|
||
// // 2. 用户选择业务 → 获取报表列表
|
||
// GET /api/report/reports?businessCode=KUAISHOU
|
||
// 返回: [{ reportCode: "shop_daily_report", reportName: "快手店铺日报" }, ...]
|
||
//
|
||
// // 3. 用户选择报表 → 获取可用字段 → 渲染维度/指标/筛选选择器
|
||
// GET /api/report/fields?businessCode=KUAISHOU&reportCode=shop_daily_report
|
||
// 返回: { dimensions: [...], indicators: [...], filters: [...] }
|
||
//
|
||
// // 4. 用户选好条件 → 查询
|
||
// POST /api/report/query
|
||
// Body: {
|
||
// "businessCode": "KUAISHOU", "reportCode": "shop_daily_report",
|
||
// "dimensions": ["shop_id", "shop_name"],
|
||
// "indicators": [
|
||
// {"fieldCode": "order_amount", "aggregate": "SUM", "alias": "total"},
|
||
// {"fieldCode": "order_count", "aggregate": "SUM", "alias": "count"}
|
||
// ],
|
||
// "timeRange": {"startDate": "2026-06-01", "endDate": "2026-06-10"},
|
||
// "orderBy": [{"fieldCode": "total", "direction": "DESC"}],
|
||
// "page": 1, "pageSize": 20
|
||
// }
|
||
// 返回: { list: [...], total: 152, page: 1, totalPages: 8, execTimeMs: 45 }
|
||
//
|
||
// // 5. 翻页/换维度/换指标 → 重新调 Step 4
|
||
//
|
||
// 管理后台(用户可自行维护配置):
|
||
//
|
||
// // 新增业务
|
||
// POST /api/report/business/save
|
||
// Body: { "businessCode": "TAOBAO", "businessName": "淘宝电商" }
|
||
//
|
||
// // 新增/修改字段(用户自定义统计维度)
|
||
// POST /api/report/field/save
|
||
// Body: { "businessCode": "TAOBAO", "reportCode": "shop_daily_report",
|
||
// "fieldCode": "category", "fieldName": "商品类目",
|
||
// "fieldType": "STRING", "fieldRole": "DIMENSION" }
|
||
//
|
||
// // 配置抽取规则
|
||
// POST /api/report/extractConfig/save
|
||
// Body: { "businessCode": "TAOBAO", "reportCode": "shop_daily_report",
|
||
// "extractCode": "extract_daily", "sourceTableName": "taobao_order_list",
|
||
// "extractMode": "AGGREGATE", "groupByFields": ["shop_id"],
|
||
// "fieldMappings": [...] }
|
||
|
||
|
||
// ============================================================================
|
||
// Direct vs AGGREGATE 模式对比
|
||
// ============================================================================
|
||
//
|
||
// ┌──────────┬──────────────────────────────────────────────┐
|
||
// │ 模式 │ 行为 │
|
||
// ├──────────┼──────────────────────────────────────────────┤
|
||
// │ DIRECT │ 逐行映射,1:1 从源表复制到目标表 │
|
||
// │ │ 适用:源表已是一行一统计 │
|
||
// │ │ SQL: SELECT ... FROM source │
|
||
// ├──────────┼──────────────────────────────────────────────┤
|
||
// │ AGGREGATE│ GROUP BY + 聚合函数,N:1 聚合 │
|
||
// │ │ 适用:源表是明细表(如订单行) │
|
||
// │ │ SQL: SELECT ... SUM() COUNT() ... │
|
||
// │ │ FROM source GROUP BY groupByFields │
|
||
// └──────────┴──────────────────────────────────────────────┘
|
||
//
|
||
// 可聚合函数: SUM / COUNT / AVG / MAX / MIN
|
||
// 字段角色: DIMENSION(维度) / INDICATOR(指标) / FILTER(筛选) / FILTER_ONLY
|
||
// 字段类型: STRING / INT / FLOAT / DATE / DATETIME / JSONB
|
||
// 操作符: = / != / > / < / >= / <= / IN / LIKE / BETWEEN
|
||
// 时间分组: day / week / month / quarter
|
||
*/
|