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