package executor import ( "context" "fmt" "math" "strings" "time" "dataengine/common/report/builder" "dataengine/common/report/model" "gitea.redpowerfuture.com/red-future/common/db/gfdb" "github.com/sirupsen/logrus" ) // QueryExecutor SQL执行器 type QueryExecutor struct { sqlBuilder *builder.SQLBuilder } // NewQueryExecutor 创建查询执行器 func NewQueryExecutor() *QueryExecutor { return &QueryExecutor{ sqlBuilder: builder.NewSQLBuilder(), } } // QueryReportByUserSelect 根据用户选择实时查询报表数据(核心接口) func (e *QueryExecutor) QueryReportByUserSelect(ctx context.Context, req *model.UserSelectQueryReq) (*model.UserSelectQueryResp, error) { start := time.Now() logger := logrus.WithFields(logrus.Fields{ "businessCode": req.BusinessCode, "reportCode": req.ReportCode, }) // 1. 参数校验 if err := e.validateReq(req); err != nil { return nil, fmt.Errorf("参数校验失败: %w", err) } // 2. 构建查询SQL querySQL, queryArgs, metadata, err := e.sqlBuilder.BuildQuerySQL(ctx, req) if err != nil { return nil, fmt.Errorf("构建查询SQL失败: %w", err) } logger.Debugf("查询SQL: %s args: %v", querySQL, queryArgs) // 3. 获取记录总数 total, err := e.executeCount(ctx, metadata, queryArgs) if err != nil { return nil, fmt.Errorf("查询总数失败: %w", err) } // 4. 分页查询 if req.Page < 1 { req.Page = 1 } if req.PageSize < 1 { req.PageSize = 20 } if req.PageSize > 1000 { req.PageSize = 1000 } pagedSQL := e.sqlBuilder.AddLimit(querySQL, req.Page, req.PageSize) logger.Debugf("分页SQL: %s", pagedSQL) dataResult, err := gfdb.DB(ctx).GetAll(ctx, pagedSQL, queryArgs...) if err != nil { return nil, fmt.Errorf("查询数据失败: %w", err) } var list []map[string]interface{} if dataResult.Len() > 0 { list = dataResult.List() } // 计算总页数 totalPages := int(math.Ceil(float64(total) / float64(req.PageSize))) execTime := time.Since(start).Milliseconds() resp := &model.UserSelectQueryResp{ List: list, Total: total, Page: req.Page, PageSize: req.PageSize, TotalPages: totalPages, Sql: querySQL, ExecTimeMs: execTime, } logger.Infof("报表查询完成, 总数:%d 耗时:%dms", total, execTime) return resp, nil } // executeCount 执行总数统计 func (e *QueryExecutor) executeCount(ctx context.Context, metadata map[string]interface{}, args []interface{}) (int64, error) { countSql, ok := metadata["countSql"].(string) if !ok || countSql == "" { return 0, nil } result, err := gfdb.DB(ctx).GetAll(ctx, countSql, args...) if err != nil { return 0, fmt.Errorf("统计总数失败: %w", err) } var total int64 if result.Len() > 0 { for k, v := range result.List()[0] { _ = k if n, ok := v.(int64); ok { total = n } else { total = result[0]["count"].Int64() } break } } return total, nil } // validateReq 校验请求参数 func (e *QueryExecutor) validateReq(req *model.UserSelectQueryReq) error { if req.BusinessCode == "" { return fmt.Errorf("业务编码不能为空") } if req.ReportCode == "" { return fmt.Errorf("报表编码不能为空") } if len(req.Indicators) == 0 { return fmt.Errorf("必须选择至少一个指标") } // 校验指标 for i, ind := range req.Indicators { if ind.FieldCode == "" { return fmt.Errorf("第%d个指标字段编码不能为空", i+1) } if ind.Aggregate != "" { agg := strings.ToUpper(ind.Aggregate) validAggs := map[string]bool{ "SUM": true, "COUNT": true, "AVG": true, "MAX": true, "MIN": true, } if !validAggs[agg] { return fmt.Errorf("不支持的聚合方式: %s", ind.Aggregate) } } } // 校验时间分组 if req.TimeGroup != "" { validGroups := map[string]bool{ "day": true, "week": true, "month": true, "quarter": true, } if !validGroups[req.TimeGroup] { return fmt.Errorf("不支持的时间分组: %s", req.TimeGroup) } } return nil } // RawQuery 执行原始SQL查询(用于特殊场景) func (e *QueryExecutor) RawQuery(ctx context.Context, sql string, args ...interface{}) ([]map[string]interface{}, error) { result, err := gfdb.DB(ctx).GetAll(ctx, sql, args...) if err != nil { return nil, fmt.Errorf("原始查询失败: %w", err) } if result.Len() > 0 { return result.List(), nil } return nil, nil } // ExecRaw 执行原始SQL(INSERT/UPDATE/DELETE) func (e *QueryExecutor) ExecRaw(ctx context.Context, sql string, args ...interface{}) error { _, err := gfdb.DB(ctx).Exec(ctx, sql, args...) return err }