478 lines
15 KiB
Go
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)
|
|
}
|