package exporter import ( "encoding/json" "errors" "fmt" "marketing-system-data-tool/server/internal/schema" "strconv" "strings" ) type BuildRequest struct { MainTable string Datasource string Fields []string // table.field Filters map[string]interface{} } func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{}, error) { if req.MainTable != "order" && req.MainTable != "order_info" { return "", nil, errors.New("unsupported main table") } sch := schema.Get(req.Datasource, req.MainTable) cols := []string{} need := map[string]bool{} for _, tf := range req.Fields { if !whitelist[tf] { return "", nil, errors.New("field not allowed") } parts := strings.Split(tf, ".") if len(parts) != 2 { return "", nil, errors.New("invalid field format") } t, f := parts[0], parts[1] need[t] = true mt := sch.TableName(t) mf, _ := sch.MapField(t, f) if t == "order" && req.MainTable == "order" { if f == "status" { cols = append(cols, "CASE `order`.type WHEN 1 THEN '直充卡密' WHEN 2 THEN '立减金' WHEN 3 THEN '红包' ELSE '' END AS type") cols = append(cols, "CASE `order`.status WHEN 0 THEN '待充值' WHEN 1 THEN '充值中' WHEN 2 THEN '已完成' WHEN 3 THEN '充值失败' WHEN 4 THEN '已取消' WHEN 5 THEN '已过期' WHEN 6 THEN '待支付' END AS status") continue } if f == "type" { cols = append(cols, "CASE `order`.type WHEN 1 THEN '直充卡密' WHEN 2 THEN '立减金' WHEN 3 THEN '红包' ELSE '' END AS type") continue } if f == "pay_type" { cols = append(cols, "CASE `order`.pay_type WHEN 1 THEN '支付宝' WHEN 5 THEN '微信' ELSE '' END AS pay_type") continue } if f == "pay_status" { cols = append(cols, "CASE `order`.pay_status WHEN 1 THEN '待支付' WHEN 2 THEN '已支付' WHEN 3 THEN '已退款' ELSE '' END AS pay_status") continue } } if t == "order_cash" && f == "receive_status" { cols = append(cols, "CASE `order_cash`.receive_status WHEN 0 THEN '待领取' WHEN 1 THEN '领取中' WHEN 2 THEN '领取成功' WHEN 3 THEN '领取失败' ELSE '' END AS receive_status") continue } if t == "order_cash" && f == "channel" { cols = append(cols, "CASE `order_cash`.channel WHEN 1 THEN '支付宝' WHEN 2 THEN '微信' WHEN 3 THEN '云闪付' ELSE '' END AS channel") continue } if t == "order_voucher" && f == "channel" { cols = append(cols, "CASE `order_voucher`.channel WHEN 1 THEN '支付宝' WHEN 2 THEN '微信' ELSE '' END AS channel") continue } if t == "order_voucher" && f == "status" { cols = append(cols, "CASE `order_voucher`.status WHEN 1 THEN '可用' WHEN 2 THEN '已实扣' WHEN 3 THEN '已过期' WHEN 4 THEN '已退款' WHEN 5 THEN '领取失败' WHEN 6 THEN '发放中' WHEN 7 THEN '部分退款' WHEN 8 THEN '已退回' WHEN 9 THEN '发放失败' ELSE '' END AS status") continue } if t == "order_voucher" && f == "receive_mode" { cols = append(cols, "CASE `order_voucher`.receive_mode WHEN 1 THEN '渠道授权用户id' WHEN 2 THEN '手机号或邮箱' ELSE '' END AS receive_mode") continue } if t == "order_voucher" && f == "out_biz_no" { cols = append(cols, "'' AS out_biz_no") continue } cols = append(cols, "`"+mt+"`."+escape(mf)) } if len(cols) == 0 { return "", nil, errors.New("no fields") } sb := strings.Builder{} sb.WriteString("SELECT ") sb.WriteString(strings.Join(cols, ",")) sb.WriteString(" FROM `" + req.MainTable + "`") for _, j := range sch.BuildJoins(need, req.MainTable) { sb.WriteString(j) } args := []interface{}{} where := []string{} // collect need from filters referencing related tables if _, ok := req.Filters["order_cash_cash_activity_id_eq"]; ok { need["order_cash"] = true } if _, ok := req.Filters["order_voucher_channel_activity_id_eq"]; ok { need["order_voucher"] = true } if _, ok := req.Filters["voucher_batch_channel_activity_id_eq"]; ok { need["voucher_batch"] = true need["voucher"] = true need["order_voucher"] = true } if _, ok := req.Filters["merchant_out_biz_no_eq"]; ok { need["merchant_key_send"] = true } if v, ok := req.Filters["creator_in"]; ok { ids := []interface{}{} switch t := v.(type) { case []interface{}: ids = t case []int: for _, x := range t { ids = append(ids, x) } case []string: for _, x := range t { ids = append(ids, x) } } if len(ids) == 0 { return "", nil, errors.New("creator_in required") } ph := strings.Repeat("?,", len(ids)) ph = strings.TrimSuffix(ph, ",") if tbl, col, ok := sch.FilterColumn("creator_in"); ok { where = append(where, fmt.Sprintf("`%s`.%s IN (%s)", sch.TableName(tbl), escape(col), ph)) } args = append(args, ids...) } if v, ok := req.Filters["create_time_between"]; ok { var arr []interface{} b, _ := json.Marshal(v) json.Unmarshal(b, &arr) if len(arr) != 2 { return "", nil, errors.New("create_time_between requires 2 values") } if tbl, col, ok := sch.FilterColumn("create_time_between"); ok { where = append(where, fmt.Sprintf("`%s`.%s BETWEEN ? AND ?", sch.TableName(tbl), escape(col))) } args = append(args, arr[0], arr[1]) } if v, ok := req.Filters["type_eq"]; ok { var tv int switch t := v.(type) { case float64: tv = int(t) case int: tv = t case string: // simple digits parsing for i := 0; i < len(t); i++ { c := t[i] if c < '0' || c > '9' { continue } tv = tv*10 + int(c-'0') } } if tv == 1 || tv == 2 || tv == 3 { if tbl, col, ok := sch.FilterColumn("type_eq"); ok { where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col))) } args = append(args, tv) } } if v, ok := req.Filters["out_trade_no_eq"]; ok { s := toString(v) if s != "" { if tbl, col, ok := sch.FilterColumn("out_trade_no_eq"); ok { where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col))) } args = append(args, s) } } if v, ok := req.Filters["account_eq"]; ok { s := toString(v) if s != "" { if tbl, col, ok := sch.FilterColumn("account_eq"); ok { where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col))) } args = append(args, s) } } if v, ok := req.Filters["plan_id_eq"]; ok { s := toString(v) if s != "" { if tbl, col, ok := sch.FilterColumn("plan_id_eq"); ok { where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col))) } args = append(args, s) } } if v, ok := req.Filters["key_batch_id_eq"]; ok { s := toString(v) if s != "" { if tbl, col, ok := sch.FilterColumn("key_batch_id_eq"); ok { where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col))) } args = append(args, s) } } if v, ok := req.Filters["product_id_eq"]; ok { s := toString(v) if s != "" { if tbl, col, ok := sch.FilterColumn("product_id_eq"); ok { where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col))) } args = append(args, s) } } if v, ok := req.Filters["reseller_id_eq"]; ok { s := toString(v) if s != "" { if tbl, col, ok := sch.FilterColumn("reseller_id_eq"); ok { where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col))) } args = append(args, s) } } if v, ok := req.Filters["code_batch_id_eq"]; ok { s := toString(v) if s != "" { if tbl, col, ok := sch.FilterColumn("code_batch_id_eq"); ok { where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col))) } args = append(args, s) } } if v, ok := req.Filters["order_cash_cash_activity_id_eq"]; ok { s := toString(v) if s != "" { if tbl, col, ok := sch.FilterColumn("order_cash_cash_activity_id_eq"); ok { where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col))) } args = append(args, s) } } if v, ok := req.Filters["order_voucher_channel_activity_id_eq"]; ok { s := toString(v) if s != "" { if tbl, col, ok := sch.FilterColumn("order_voucher_channel_activity_id_eq"); ok { where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col))) } args = append(args, s) } } if v, ok := req.Filters["voucher_batch_channel_activity_id_eq"]; ok { s := toString(v) if s != "" { if tbl, col, ok := sch.FilterColumn("voucher_batch_channel_activity_id_eq"); ok { where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col))) args = append(args, s) } } } if v, ok := req.Filters["merchant_out_biz_no_eq"]; ok { s := toString(v) if s != "" { if tbl, col, ok := sch.FilterColumn("merchant_out_biz_no_eq"); ok { where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col))) } args = append(args, s) } } if len(where) > 0 { sb.WriteString(" WHERE ") sb.WriteString(strings.Join(where, " AND ")) } return sb.String(), args, nil } func escape(s string) string { if s == "key" { return "`key`" } if s == "index" { return "`index`" } return s } func toString(v interface{}) string { switch t := v.(type) { case []byte: return string(t) case string: return t case int64: return strconv.FormatInt(t, 10) case int: return strconv.Itoa(t) case float64: return strconv.FormatFloat(t, 'f', -1, 64) default: return "" } }