重构数据引擎和报表引擎

This commit is contained in:
2026-06-11 13:06:54 +08:00
parent 285a0fc632
commit 419473f266
53 changed files with 8434 additions and 375 deletions

View File

@@ -0,0 +1,666 @@
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)
// 实际抽取生成的 SQLAGGREGATE 模式):
//
// 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
*/