Files
data-engine/common/report/api.go
2026-06-11 13:06:54 +08:00

478 lines
15 KiB
Go

package report
import (
"context"
"fmt"
"strings"
"dataengine/common/report/config"
"dataengine/common/report/ddlsync"
"dataengine/common/report/executor"
"dataengine/common/report/extract"
"dataengine/common/report/model"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
)
// ReportService 报表公共服务
// 对外暴露的统一接口
type ReportService struct {
configLoader *config.ConfigLoader
tableCreator *ddlsync.StatTableCreator
queryExecutor *executor.QueryExecutor
dailyExtractor *extract.DailyExtractor
}
var defaultService *ReportService
// GetService 获取报表服务单例
func GetService() *ReportService {
if defaultService == nil {
defaultService = &ReportService{
configLoader: config.GetLoader(),
tableCreator: ddlsync.NewStatTableCreator(),
queryExecutor: executor.NewQueryExecutor(),
dailyExtractor: extract.NewDailyExtractor(),
}
}
return defaultService
}
// ============================================================
// 核心接口 1: 自动创建统计宽表
// 首次抽取前调用
// ============================================================
// AutoCreateStatTable 根据配置自动创建统计宽表
// businessCode: 业务编码
// reportCode: 报表编码
func (s *ReportService) AutoCreateStatTable(ctx context.Context, businessCode, reportCode string) (*model.AutoCreateStatTableResp, error) {
// 初始化系统表
if err := initTables(ctx); err != nil {
return nil, fmt.Errorf("初始化系统表失败: %w", err)
}
resp, err := s.tableCreator.AutoCreateStatTable(ctx, businessCode, reportCode)
if err != nil {
return nil, fmt.Errorf("AutoCreateStatTable 失败: %w", err)
}
return resp, nil
}
// ============================================================
// 核心接口 2: 按天抽取数据
// 业务层定时任务调用
// ============================================================
// ExtractDailyData 按天抽取数据
// businessCode: 业务编码
// reportCode: 报表编码
// statDate: 统计日期 yyyy-MM-dd
// executor: 执行人
func (s *ReportService) ExtractDailyData(ctx context.Context, businessCode, reportCode, statDate, executor string) (*model.ExtractDailyDataResp, error) {
// 1. 先确保统计宽表存在
report, err := s.configLoader.GetReport(ctx, businessCode, reportCode)
if err != nil {
return nil, fmt.Errorf("获取报表配置失败: %w", err)
}
// 检查表是否存在
result, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = $1) AS exists",
strings.ToLower(report.StatTableName))
if err != nil {
return nil, fmt.Errorf("检查统计宽表失败: %w", err)
}
tableExists := false
if len(result) > 0 {
tableExists = result[0]["exists"].Bool()
}
if !tableExists {
// 表不存在,先创建
if _, createErr := s.AutoCreateStatTable(ctx, businessCode, reportCode); createErr != nil {
return nil, fmt.Errorf("创建统计宽表失败: %w", createErr)
}
}
resp, err := s.dailyExtractor.ExtractDailyData(ctx, businessCode, reportCode, statDate, executor)
if err != nil {
return nil, fmt.Errorf("ExtractDailyData 失败: %w", err)
}
// 清除缓存
s.configLoader.InvalidateCache(businessCode, reportCode)
return resp, nil
}
// ============================================================
// 核心接口 3: 用户选择查询(最核心)
// 前端用户选择条件 → 实时构建SQL → 返回报表数据
// ============================================================
// QueryReportByUserSelect 根据用户选择实时查询报表数据
// 不是自动生成报表,是用户在前端选择维度/指标/筛选/时间后实时查询展示
func (s *ReportService) QueryReportByUserSelect(ctx context.Context, req *model.UserSelectQueryReq) (*model.UserSelectQueryResp, error) {
// 参数校验
if req.BusinessCode == "" {
return nil, fmt.Errorf("businessCode 不能为空")
}
if req.ReportCode == "" {
return nil, fmt.Errorf("reportCode 不能为空")
}
resp, err := s.queryExecutor.QueryReportByUserSelect(ctx, req)
if err != nil {
return nil, fmt.Errorf("QueryReportByUserSelect 失败: %w", err)
}
return resp, nil
}
// ============================================================
// 辅助接口
// ============================================================
// GetReportFields 获取报表可用字段(按维度/指标/筛选分类)
func (s *ReportService) GetReportFields(ctx context.Context, businessCode, reportCode string) (*model.GetReportFieldsResp, error) {
resp, err := s.configLoader.GetReportFields(ctx, businessCode, reportCode)
if err != nil {
return nil, fmt.Errorf("GetReportFields 失败: %w", err)
}
return resp, nil
}
// GetAllBusinesses 获取所有启用业务列表
func (s *ReportService) GetAllBusinesses(ctx context.Context) ([]model.BusinessConfig, error) {
return s.configLoader.GetAllBusinesses(ctx)
}
// GetAllReports 获取业务下所有报表列表
func (s *ReportService) GetAllReports(ctx context.Context, businessCode string) ([]model.ReportConfig, error) {
return s.configLoader.GetAllReports(ctx, businessCode)
}
// InvalidateCache 失效指定业务报表缓存
func (s *ReportService) InvalidateCache(businessCode, reportCode string) {
s.configLoader.InvalidateCache(businessCode, reportCode)
}
// InitSystemTables 初始化系统表
func (s *ReportService) InitSystemTables(ctx context.Context) error {
return initTables(ctx)
}
// ============================================================
// 配置 CRUD: 业务
// ============================================================
// SaveBusiness 保存业务配置(新增/修改合一)
func (s *ReportService) SaveBusiness(ctx context.Context, req *model.SaveBusinessReq) (*model.SaveResult, error) {
if err := initTables(ctx); err != nil {
return nil, fmt.Errorf("初始化系统表失败: %w", err)
}
biz := &model.BusinessConfig{
BusinessCode: req.BusinessCode,
BusinessName: req.BusinessName,
Description: req.Description,
Status: req.Status,
Config: req.Config,
Creator: req.Operator,
Updater: req.Operator,
}
if req.Status == "" {
biz.Status = model.StatusActive
}
if biz.Config == nil {
biz.Config = make(map[string]interface{})
}
if req.ID != nil && *req.ID > 0 {
// 更新
biz.ID = *req.ID
if err := s.configLoader.UpdateBusiness(ctx, biz); err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: *req.ID, Message: "更新成功"}, nil
}
// 新增
id, err := s.configLoader.CreateBusiness(ctx, biz)
if err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: id, Message: "创建成功"}, nil
}
// DeleteBusiness 删除业务配置
func (s *ReportService) DeleteBusiness(ctx context.Context, id int64) (*model.DeleteResult, error) {
biz, err := s.configLoader.GetBusinessByID(ctx, id)
if err != nil {
return nil, err
}
if err := s.configLoader.DeleteBusiness(ctx, id, biz.BusinessCode); err != nil {
return nil, err
}
return &model.DeleteResult{Success: true, Message: "删除成功"}, nil
}
// GetBusiness 获取单个业务配置
func (s *ReportService) GetBusiness(ctx context.Context, id int64) (*model.BusinessConfig, error) {
return s.configLoader.GetBusinessByID(ctx, id)
}
// ============================================================
// 配置 CRUD: 报表
// ============================================================
// SaveReport 保存报表配置(新增/修改合一)
func (s *ReportService) SaveReport(ctx context.Context, req *model.SaveReportReq) (*model.SaveResult, error) {
if err := initTables(ctx); err != nil {
return nil, fmt.Errorf("初始化系统表失败: %w", err)
}
rpt := &model.ReportConfig{
BusinessCode: req.BusinessCode,
ReportCode: req.ReportCode,
ReportName: req.ReportName,
Description: req.Description,
Status: req.Status,
StatTableName: req.StatTableName,
StatTableComment: req.StatTableComment,
DateField: req.DateField,
PrimaryKeys: req.PrimaryKeys,
ConflictKeys: req.ConflictKeys,
Config: req.Config,
Creator: req.Operator,
Updater: req.Operator,
}
if req.Status == "" {
rpt.Status = model.StatusActive
}
if rpt.DateField == "" {
rpt.DateField = "stat_date"
}
if rpt.PrimaryKeys == nil {
rpt.PrimaryKeys = []string{"id"}
}
if rpt.ConflictKeys == nil {
rpt.ConflictKeys = []string{rpt.DateField}
}
if rpt.Config == nil {
rpt.Config = make(map[string]interface{})
}
if req.ID != nil && *req.ID > 0 {
rpt.ID = *req.ID
if err := s.configLoader.UpdateReport(ctx, rpt); err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: *req.ID, Message: "更新成功"}, nil
}
id, err := s.configLoader.CreateReport(ctx, rpt)
if err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: id, Message: "创建成功"}, nil
}
// DeleteReport 删除报表配置
func (s *ReportService) DeleteReport(ctx context.Context, id int64) (*model.DeleteResult, error) {
rpt, err := s.configLoader.GetReportByID(ctx, id)
if err != nil {
return nil, err
}
if err := s.configLoader.DeleteReport(ctx, id, rpt.BusinessCode, rpt.ReportCode); err != nil {
return nil, err
}
return &model.DeleteResult{Success: true, Message: "删除成功"}, nil
}
// GetReport 获取单个报表配置
func (s *ReportService) GetReport(ctx context.Context, id int64) (*model.ReportConfig, error) {
return s.configLoader.GetReportByID(ctx, id)
}
// ============================================================
// 配置 CRUD: 字段
// ============================================================
// SaveField 保存字段配置(新增/修改合一)
func (s *ReportService) SaveField(ctx context.Context, req *model.SaveFieldReq) (*model.SaveResult, error) {
if err := initTables(ctx); err != nil {
return nil, fmt.Errorf("初始化系统表失败: %w", err)
}
field := &model.FieldConfig{
BusinessCode: req.BusinessCode,
ReportCode: req.ReportCode,
FieldCode: req.FieldCode,
FieldName: req.FieldName,
FieldType: req.FieldType,
DataType: req.DataType,
FieldRole: req.FieldRole,
IsAggregatable: req.IsAggregatable,
IsFilterable: req.IsFilterable,
IsQueryable: req.IsQueryable,
IsSortable: req.IsSortable,
DefaultAggregate: req.DefaultAggregate,
ValidAggregates: req.ValidAggregates,
FilterOperators: req.FilterOperators,
Expression: req.Expression,
ExpressionType: req.ExpressionType,
FormatPattern: req.FormatPattern,
Unit: req.Unit,
DictCode: req.DictCode,
SortOrder: req.SortOrder,
GroupName: req.GroupName,
Status: req.Status,
Creator: req.Operator,
Updater: req.Operator,
}
if req.Status == "" {
field.Status = model.StatusActive
}
if field.DataType == "" {
field.DataType = model.FieldTypeString
}
if field.ValidAggregates == nil {
field.ValidAggregates = []string{}
}
if field.FilterOperators == nil {
field.FilterOperators = []string{"=", "!=", ">", "<", ">=", "<=", "IN", "LIKE", "BETWEEN"}
}
if req.ID != nil && *req.ID > 0 {
field.ID = *req.ID
if err := s.configLoader.UpdateField(ctx, field); err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: *req.ID, Message: "更新成功"}, nil
}
id, err := s.configLoader.CreateField(ctx, field)
if err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: id, Message: "创建成功"}, nil
}
// DeleteField 删除字段配置
func (s *ReportService) DeleteField(ctx context.Context, id int64) (*model.DeleteResult, error) {
field, err := s.configLoader.GetFieldByID(ctx, id)
if err != nil {
return nil, err
}
if err := s.configLoader.DeleteField(ctx, id, field.BusinessCode, field.ReportCode); err != nil {
return nil, err
}
return &model.DeleteResult{Success: true, Message: "删除成功"}, nil
}
// GetField 获取单个字段配置
func (s *ReportService) GetField(ctx context.Context, id int64) (*model.FieldConfig, error) {
return s.configLoader.GetFieldByID(ctx, id)
}
// ============================================================
// 配置 CRUD: 抽取配置
// ============================================================
// SaveExtractConfig 保存抽取配置(新增/修改合一)
func (s *ReportService) SaveExtractConfig(ctx context.Context, req *model.SaveExtractConfigReq) (*model.SaveResult, error) {
if err := initTables(ctx); err != nil {
return nil, fmt.Errorf("初始化系统表失败: %w", err)
}
ec := &model.ExtractConfig{
BusinessCode: req.BusinessCode,
ReportCode: req.ReportCode,
ExtractCode: req.ExtractCode,
ExtractName: req.ExtractName,
SourceTableName: req.SourceTableName,
SourceTableAlias: req.SourceTableAlias,
TargetTableName: req.TargetTableName,
IsEnabled: req.IsEnabled,
ExtractType: req.ExtractType,
ExtractMode: req.ExtractMode,
ExtractKeyField: req.ExtractKeyField,
ExtractKeyFormat: req.ExtractKeyFormat,
GroupByFields: req.GroupByFields,
FilterExpression: req.FilterExpression,
JoinConfigs: req.JoinConfigs,
FieldMappings: req.FieldMappings,
TransformRules: req.TransformRules,
BatchSize: req.BatchSize,
Status: req.Status,
Creator: req.Operator,
Updater: req.Operator,
}
if req.Status == "" {
ec.Status = model.StatusActive
}
if ec.ExtractType == "" {
ec.ExtractType = model.ExtractTypeIncremental
}
if ec.ExtractMode == "" {
ec.ExtractMode = model.ExtractModeDirect
}
if ec.BatchSize == 0 {
ec.BatchSize = 1000
}
if ec.JoinConfigs == nil {
ec.JoinConfigs = []model.JoinConfig{}
}
if ec.FieldMappings == nil {
ec.FieldMappings = []model.FieldMapping{}
}
if ec.TransformRules == nil {
ec.TransformRules = []model.TransformRule{}
}
if ec.GroupByFields == nil {
ec.GroupByFields = []string{}
}
if req.ID != nil && *req.ID > 0 {
ec.ID = *req.ID
if err := s.configLoader.UpdateExtractConfig(ctx, ec); err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: *req.ID, Message: "更新成功"}, nil
}
id, err := s.configLoader.CreateExtractConfig(ctx, ec)
if err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: id, Message: "创建成功"}, nil
}
// DeleteExtractConfig 删除抽取配置
func (s *ReportService) DeleteExtractConfig(ctx context.Context, id int64) (*model.DeleteResult, error) {
ec, err := s.configLoader.GetExtractConfigByID(ctx, id)
if err != nil {
return nil, err
}
if err := s.configLoader.DeleteExtractConfig(ctx, id, ec.BusinessCode, ec.ReportCode); err != nil {
return nil, err
}
return &model.DeleteResult{Success: true, Message: "删除成功"}, nil
}
// GetExtractConfig 获取单个抽取配置
func (s *ReportService) GetExtractConfig(ctx context.Context, id int64) (*model.ExtractConfig, error) {
return s.configLoader.GetExtractConfigByID(ctx, id)
}
// GetExtractConfigs 获取业务报表下所有抽取配置
func (s *ReportService) GetExtractConfigs(ctx context.Context, businessCode, reportCode string) ([]model.ExtractConfig, error) {
return s.configLoader.GetExtractConfigs(ctx, businessCode, reportCode)
}