193 lines
4.5 KiB
Go
193 lines
4.5 KiB
Go
|
|
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
|
|||
|
|
}
|