Files
data-engine/common/report/executor/executor.go

193 lines
4.5 KiB
Go
Raw Normal View History

2026-06-11 13:06:54 +08:00
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 执行原始SQLINSERT/UPDATE/DELETE
func (e *QueryExecutor) ExecRaw(ctx context.Context, sql string, args ...interface{}) error {
_, err := gfdb.DB(ctx).Exec(ctx, sql, args...)
return err
}