refactor(exporter): 重构SQL构建逻辑以支持多数据源

将SQL构建逻辑重构为基于schema接口的实现,支持不同数据源的字段映射和表连接
修复重复转换行数据的问题
公开Whitelist和FieldLabels函数以供外部调用
This commit is contained in:
zhouyonggao 2025-11-27 14:21:29 +08:00
parent 4467bc536d
commit 2ac2d61551
8 changed files with 344 additions and 279 deletions

Binary file not shown.

View File

@ -96,7 +96,7 @@ func (a *ExportsAPI) create(w http.ResponseWriter, r *http.Request) {
} }
var fs []string var fs []string
json.Unmarshal(fields, &fs) json.Unmarshal(fields, &fs)
wl := whitelist() wl := Whitelist()
req := exporter.BuildRequest{MainTable: main, Datasource: ds, Fields: fs, Filters: p.Filters} req := exporter.BuildRequest{MainTable: main, Datasource: ds, Fields: fs, Filters: p.Filters}
q, args, err := exporter.BuildSQL(req, wl) q, args, err := exporter.BuildSQL(req, wl)
if err != nil { if err != nil {
@ -138,7 +138,7 @@ func (a *ExportsAPI) create(w http.ResponseWriter, r *http.Request) {
} }
} }
}() }()
labels := fieldLabels() labels := FieldLabels()
hdrs := make([]string, len(fs)) hdrs := make([]string, len(fs))
for i, tf := range fs { for i, tf := range fs {
if v, ok := labels[tf]; ok { if v, ok := labels[tf]; ok {
@ -150,7 +150,9 @@ func (a *ExportsAPI) create(w http.ResponseWriter, r *http.Request) {
// owner from query userId if provided // owner from query userId if provided
owner := uint64(0) owner := uint64(0)
if uidStr := r.URL.Query().Get("userId"); uidStr != "" { if uidStr := r.URL.Query().Get("userId"); uidStr != "" {
if n, err := strconv.ParseUint(uidStr, 10, 64); err == nil { owner = n } if n, err := strconv.ParseUint(uidStr, 10, 64); err == nil {
owner = n
}
} }
ejSQL := "INSERT INTO export_jobs (template_id, status, requested_by, owner_id, permission_scope_json, filters_json, options_json, explain_json, explain_score, row_estimate, file_format, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)" ejSQL := "INSERT INTO export_jobs (template_id, status, requested_by, owner_id, permission_scope_json, filters_json, options_json, explain_json, explain_score, row_estimate, file_format, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)"
ejArgs := []interface{}{p.TemplateID, "queued", p.RequestedBy, owner, toJSON(p.Permission), toJSON(p.Filters), toJSON(p.Options), toJSON(expRows), score, estimate, p.FileFormat, time.Now(), time.Now()} ejArgs := []interface{}{p.TemplateID, "queued", p.RequestedBy, owner, toJSON(p.Permission), toJSON(p.Filters), toJSON(p.Options), toJSON(expRows), score, estimate, p.FileFormat, time.Now(), time.Now()}
@ -191,7 +193,7 @@ func (a *ExportsAPI) runJob(id uint64, db *sql.DB, q string, args []interface{},
var fl map[string]interface{} var fl map[string]interface{}
json.Unmarshal(fieldsJSON, &fs) json.Unmarshal(fieldsJSON, &fs)
json.Unmarshal(filtersJSON, &fl) json.Unmarshal(filtersJSON, &fl)
wl := whitelist() wl := Whitelist()
var chunks [][2]string var chunks [][2]string
if v, ok := fl["create_time_between"]; ok { if v, ok := fl["create_time_between"]; ok {
if arr, ok2 := v.([]interface{}); ok2 && len(arr) == 2 { if arr, ok2 := v.([]interface{}); ok2 && len(arr) == 2 {
@ -244,9 +246,6 @@ func (a *ExportsAPI) runJob(id uint64, db *sql.DB, q string, args []interface{},
} }
} }
vals = transformRow(fs, vals) vals = transformRow(fs, vals)
vals = transformRow(fields, vals)
vals = transformRow(fields, vals)
vals = transformRow(fields, vals)
w.WriteRow(vals) w.WriteRow(vals)
count++ count++
partCount++ partCount++
@ -525,7 +524,7 @@ func (a *ExportsAPI) runJob(id uint64, db *sql.DB, q string, args []interface{},
var fl map[string]interface{} var fl map[string]interface{}
json.Unmarshal(fieldsJSON, &fs) json.Unmarshal(fieldsJSON, &fs)
json.Unmarshal(filtersJSON, &fl) json.Unmarshal(filtersJSON, &fl)
wl := whitelist() wl := Whitelist()
var chunks [][2]string var chunks [][2]string
if v, ok := fl["create_time_between"]; ok { if v, ok := fl["create_time_between"]; ok {
if arr, ok2 := v.([]interface{}); ok2 && len(arr) == 2 { if arr, ok2 := v.([]interface{}); ok2 && len(arr) == 2 {
@ -578,10 +577,6 @@ func (a *ExportsAPI) runJob(id uint64, db *sql.DB, q string, args []interface{},
} }
} }
vals = transformRow(fs, vals) vals = transformRow(fs, vals)
vals = transformRow(fs, vals)
vals = transformRow(fs, vals)
vals = transformRow(fs, vals)
vals = transformRow(fields, vals)
x.WriteRow(vals) x.WriteRow(vals)
count++ count++
partCount++ partCount++
@ -804,7 +799,7 @@ func (a *ExportsAPI) getSQL(w http.ResponseWriter, r *http.Request, id string) {
var fl map[string]interface{} var fl map[string]interface{}
json.Unmarshal(fields, &fs) json.Unmarshal(fields, &fs)
json.Unmarshal(filters, &fl) json.Unmarshal(filters, &fl)
wl := whitelist() wl := Whitelist()
req := exporter.BuildRequest{MainTable: main, Datasource: ds, Fields: fs, Filters: fl} req := exporter.BuildRequest{MainTable: main, Datasource: ds, Fields: fs, Filters: fl}
q, args, err := exporter.BuildSQL(req, wl) q, args, err := exporter.BuildSQL(req, wl)
if err != nil { if err != nil {

View File

@ -238,7 +238,7 @@ func fromJSON(b []byte) interface{} {
return v return v
} }
func whitelist() map[string]bool { func Whitelist() map[string]bool {
m := map[string]bool{ m := map[string]bool{
"order.order_number": true, "order.order_number": true,
"order.key": true, "order.key": true,
@ -460,7 +460,7 @@ func whitelist() map[string]bool {
return m return m
} }
func fieldLabels() map[string]string { func FieldLabels() map[string]string {
return map[string]string{ return map[string]string{
"order.order_number": "订单编号", "order.order_number": "订单编号",
"order.key": "KEY", "order.key": "KEY",

View File

@ -3,6 +3,8 @@ package exporter
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"marketing-system-data-tool/server/internal/schema"
"strconv" "strconv"
"strings" "strings"
) )
@ -18,6 +20,7 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
if req.MainTable != "order" && req.MainTable != "order_info" { if req.MainTable != "order" && req.MainTable != "order_info" {
return "", nil, errors.New("unsupported main table") return "", nil, errors.New("unsupported main table")
} }
sch := schema.Get(req.Datasource, req.MainTable)
cols := []string{} cols := []string{}
need := map[string]bool{} need := map[string]bool{}
for _, tf := range req.Fields { for _, tf := range req.Fields {
@ -30,56 +33,52 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
} }
t, f := parts[0], parts[1] t, f := parts[0], parts[1]
need[t] = true need[t] = true
if t == "order" { mt := sch.TableName(t)
if req.MainTable == "order_info" { mf, _ := sch.MapField(t, f)
switch f { if t == "order" && req.MainTable == "order" {
case "order_number": f = "order_no" if f == "status" {
case "key": f = "key_code" cols = append(cols, "CASE `order`.type WHEN 1 THEN '直充卡密' WHEN 2 THEN '立减金' WHEN 3 THEN '红包' ELSE '' END AS type")
case "creator": f = "user_id" 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")
case "out_trade_no": f = "out_order_no"
case "plan_id": f = "activity_id"
case "reseller_id": f = "merchant_id"
case "product_id": f = "goods_id"
case "pay_amount": f = "pay_price"
case "key_batch_id": f = "key_batch_name"
}
cols = append(cols, "`order_info`."+escape(f))
continue continue
} }
if f == "status" { if f == "type" {
cols = append(cols, "CASE `order`.type " +
"WHEN 1 THEN (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) " +
"WHEN 2 THEN (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) " +
"WHEN 3 THEN (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) " +
"ELSE (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) END AS status")
} else if f == "type" {
cols = append(cols, "CASE `order`.type WHEN 1 THEN '直充卡密' WHEN 2 THEN '立减金' WHEN 3 THEN '红包' ELSE '' END AS type") cols = append(cols, "CASE `order`.type WHEN 1 THEN '直充卡密' WHEN 2 THEN '立减金' WHEN 3 THEN '红包' ELSE '' END AS type")
} else if f == "type" { continue
cols = append(cols, "CASE `order`.type WHEN 1 THEN '直充卡密' WHEN 2 THEN '立减金' WHEN 3 THEN '红包' ELSE '' END AS type") }
} else if f == "pay_type" { if f == "pay_type" {
cols = append(cols, "CASE `order`.pay_type WHEN 1 THEN '支付宝' WHEN 5 THEN '微信' ELSE '' END AS pay_type") cols = append(cols, "CASE `order`.pay_type WHEN 1 THEN '支付宝' WHEN 5 THEN '微信' ELSE '' END AS pay_type")
} else if f == "pay_status" { continue
cols = append(cols, "CASE `order`.pay_status WHEN 1 THEN '待支付' WHEN 2 THEN '已支付' WHEN 3 THEN '已退款' ELSE '' END AS pay_status") }
} else { if f == "pay_status" {
cols = append(cols, "`order`."+escape(f)) cols = append(cols, "CASE `order`.pay_status WHEN 1 THEN '待支付' WHEN 2 THEN '已支付' WHEN 3 THEN '已退款' ELSE '' END AS pay_status")
continue
}
} }
} else {
if t == "order_cash" && f == "receive_status" { 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") 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")
} else if t == "order_cash" && f == "channel" { 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") cols = append(cols, "CASE `order_cash`.channel WHEN 1 THEN '支付宝' WHEN 2 THEN '微信' WHEN 3 THEN '云闪付' ELSE '' END AS channel")
} else if t == "order_voucher" && f == "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") cols = append(cols, "CASE `order_voucher`.channel WHEN 1 THEN '支付宝' WHEN 2 THEN '微信' ELSE '' END AS channel")
} else if t == "order_voucher" && f == "status" { 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") 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")
} else if t == "order_voucher" && f == "receive_mode" { 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") cols = append(cols, "CASE `order_voucher`.receive_mode WHEN 1 THEN '渠道授权用户id' WHEN 2 THEN '手机号或邮箱' ELSE '' END AS receive_mode")
} else if t == "order_voucher" && f == "out_biz_no" { continue
}
if t == "order_voucher" && f == "out_biz_no" {
cols = append(cols, "'' AS out_biz_no") cols = append(cols, "'' AS out_biz_no")
} else { continue
cols = append(cols, "`"+t+"`."+escape(f))
}
} }
cols = append(cols, "`"+mt+"`."+escape(mf))
} }
if len(cols) == 0 { if len(cols) == 0 {
return "", nil, errors.New("no fields") return "", nil, errors.New("no fields")
@ -88,67 +87,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
sb.WriteString("SELECT ") sb.WriteString("SELECT ")
sb.WriteString(strings.Join(cols, ",")) sb.WriteString(strings.Join(cols, ","))
sb.WriteString(" FROM `" + req.MainTable + "`") sb.WriteString(" FROM `" + req.MainTable + "`")
// JOINs based on need for _, j := range sch.BuildJoins(need, req.MainTable) {
// order_detail sb.WriteString(j)
if need["order_detail"] && req.MainTable == "order" {
sb.WriteString(" LEFT JOIN `order_detail` ON `order_detail`.order_number = `order`.order_number")
}
// order_cash
if need["order_cash"] {
if req.MainTable == "order_info" {
sb.WriteString(" LEFT JOIN `order_cash` ON `order_cash`.order_no = `order_info`.order_no")
} else {
sb.WriteString(" LEFT JOIN `order_cash` ON `order_cash`.order_number = `order`.order_number")
}
}
// order_voucher
if need["order_voucher"] {
if req.MainTable == "order_info" {
sb.WriteString(" LEFT JOIN `order_voucher` ON `order_voucher`.order_no = `order_info`.order_no")
} else {
sb.WriteString(" LEFT JOIN `order_voucher` ON `order_voucher`.order_number = `order`.order_number")
}
}
// order_digit (ymt only)
if need["order_digit"] && req.MainTable == "order_info" {
sb.WriteString(" LEFT JOIN `order_digit` ON `order_digit`.order_no = `order_info`.order_no")
}
// goods_voucher_batch (ymt)
if need["goods_voucher_batch"] && req.MainTable == "order_info" {
sb.WriteString(" LEFT JOIN `goods_voucher_batch` ON `goods_voucher_batch`.channel_batch_no = `order_voucher`.channel_batch_no")
}
// goods_voucher_subject_config (ymt)
if need["goods_voucher_subject_config"] && req.MainTable == "order_info" {
sb.WriteString(" LEFT JOIN `goods_voucher_subject_config` ON `goods_voucher_subject_config`.id = `goods_voucher_batch`.voucher_subject_id")
}
// merchant (ymt)
if need["merchant"] && req.MainTable == "order_info" {
sb.WriteString(" LEFT JOIN `merchant` ON `merchant`.id = `order_info`.merchant_id")
}
// activity (ymt)
if need["activity"] && req.MainTable == "order_info" {
sb.WriteString(" LEFT JOIN `activity` ON `activity`.id = `order_info`.activity_id")
}
// plan
if req.MainTable == "order" {
if need["plan"] || need["key_batch"] {
sb.WriteString(" LEFT JOIN `plan` ON `plan`.id = `order`.plan_id")
}
if need["key_batch"] {
sb.WriteString(" LEFT JOIN `key_batch` ON `key_batch`.plan_id = `plan`.id")
}
if need["code_batch"] {
sb.WriteString(" LEFT JOIN `code_batch` ON `code_batch`.key_batch_id = `key_batch`.id")
}
if need["voucher"] {
sb.WriteString(" LEFT JOIN `voucher` ON `voucher`.channel_activity_id = `order_voucher`.channel_activity_id")
}
if need["voucher_batch"] {
sb.WriteString(" LEFT JOIN `voucher_batch` ON `voucher_batch`.voucher_id = `voucher`.id")
}
if need["merchant_key_send"] {
sb.WriteString(" LEFT JOIN `merchant_key_send` ON `order`." + escape("key") + " = `merchant_key_send`.key")
}
} }
args := []interface{}{} args := []interface{}{}
@ -187,10 +127,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
} }
ph := strings.Repeat("?,", len(ids)) ph := strings.Repeat("?,", len(ids))
ph = strings.TrimSuffix(ph, ",") ph = strings.TrimSuffix(ph, ",")
if req.MainTable == "order_info" { if tbl, col, ok := sch.FilterColumn("creator_in"); ok {
where = append(where, "`order_info`.user_id IN ("+ph+")") where = append(where, fmt.Sprintf("`%s`.%s IN (%s)", sch.TableName(tbl), escape(col), ph))
} else {
where = append(where, "`order`.creator IN ("+ph+")")
} }
args = append(args, ids...) args = append(args, ids...)
} }
@ -201,10 +139,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
if len(arr) != 2 { if len(arr) != 2 {
return "", nil, errors.New("create_time_between requires 2 values") return "", nil, errors.New("create_time_between requires 2 values")
} }
if req.MainTable == "order_info" { if tbl, col, ok := sch.FilterColumn("create_time_between"); ok {
where = append(where, "`order_info`.create_time BETWEEN ? AND ?") where = append(where, fmt.Sprintf("`%s`.%s BETWEEN ? AND ?", sch.TableName(tbl), escape(col)))
} else {
where = append(where, "`order`.create_time BETWEEN ? AND ?")
} }
args = append(args, arr[0], arr[1]) args = append(args, arr[0], arr[1])
} }
@ -226,10 +162,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
} }
} }
if tv == 1 || tv == 2 || tv == 3 { if tv == 1 || tv == 2 || tv == 3 {
if req.MainTable == "order_info" { if tbl, col, ok := sch.FilterColumn("type_eq"); ok {
where = append(where, "`order_info`.type = ?") where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
} else {
where = append(where, "`order`.type = ?")
} }
args = append(args, tv) args = append(args, tv)
} }
@ -237,10 +171,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
if v, ok := req.Filters["out_trade_no_eq"]; ok { if v, ok := req.Filters["out_trade_no_eq"]; ok {
s := toString(v) s := toString(v)
if s != "" { if s != "" {
if req.MainTable == "order_info" { if tbl, col, ok := sch.FilterColumn("out_trade_no_eq"); ok {
where = append(where, "`order_info`.out_order_no = ?") where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
} else {
where = append(where, "`order`.out_trade_no = ?")
} }
args = append(args, s) args = append(args, s)
} }
@ -248,10 +180,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
if v, ok := req.Filters["account_eq"]; ok { if v, ok := req.Filters["account_eq"]; ok {
s := toString(v) s := toString(v)
if s != "" { if s != "" {
if req.MainTable == "order_info" { if tbl, col, ok := sch.FilterColumn("account_eq"); ok {
where = append(where, "`order_info`.account = ?") where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
} else {
where = append(where, "`order`.account = ?")
} }
args = append(args, s) args = append(args, s)
} }
@ -259,10 +189,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
if v, ok := req.Filters["plan_id_eq"]; ok { if v, ok := req.Filters["plan_id_eq"]; ok {
s := toString(v) s := toString(v)
if s != "" { if s != "" {
if req.MainTable == "order_info" { if tbl, col, ok := sch.FilterColumn("plan_id_eq"); ok {
where = append(where, "`order_info`.activity_id = ?") where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
} else {
where = append(where, "`order`.plan_id = ?")
} }
args = append(args, s) args = append(args, s)
} }
@ -270,10 +198,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
if v, ok := req.Filters["key_batch_id_eq"]; ok { if v, ok := req.Filters["key_batch_id_eq"]; ok {
s := toString(v) s := toString(v)
if s != "" { if s != "" {
if req.MainTable == "order_info" { if tbl, col, ok := sch.FilterColumn("key_batch_id_eq"); ok {
where = append(where, "`order_info`.key_batch_name = ?") where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
} else {
where = append(where, "`order`.key_batch_id = ?")
} }
args = append(args, s) args = append(args, s)
} }
@ -281,10 +207,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
if v, ok := req.Filters["product_id_eq"]; ok { if v, ok := req.Filters["product_id_eq"]; ok {
s := toString(v) s := toString(v)
if s != "" { if s != "" {
if req.MainTable == "order_info" { if tbl, col, ok := sch.FilterColumn("product_id_eq"); ok {
where = append(where, "`order_info`.goods_id = ?") where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
} else {
where = append(where, "`order`.product_id = ?")
} }
args = append(args, s) args = append(args, s)
} }
@ -292,10 +216,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
if v, ok := req.Filters["reseller_id_eq"]; ok { if v, ok := req.Filters["reseller_id_eq"]; ok {
s := toString(v) s := toString(v)
if s != "" { if s != "" {
if req.MainTable == "order_info" { if tbl, col, ok := sch.FilterColumn("reseller_id_eq"); ok {
where = append(where, "`order_info`.merchant_id = ?") where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
} else {
where = append(where, "`order`.reseller_id = ?")
} }
args = append(args, s) args = append(args, s)
} }
@ -303,10 +225,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
if v, ok := req.Filters["code_batch_id_eq"]; ok { if v, ok := req.Filters["code_batch_id_eq"]; ok {
s := toString(v) s := toString(v)
if s != "" { if s != "" {
if req.MainTable == "order_info" { if tbl, col, ok := sch.FilterColumn("code_batch_id_eq"); ok {
where = append(where, "`order_info`.supplier_product_id = ?") where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
} else {
where = append(where, "`order`.code_batch_id = ?")
} }
args = append(args, s) args = append(args, s)
} }
@ -314,10 +234,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
if v, ok := req.Filters["order_cash_cash_activity_id_eq"]; ok { if v, ok := req.Filters["order_cash_cash_activity_id_eq"]; ok {
s := toString(v) s := toString(v)
if s != "" { if s != "" {
if req.MainTable == "order_info" { if tbl, col, ok := sch.FilterColumn("order_cash_cash_activity_id_eq"); ok {
where = append(where, "`order_cash`.activity_id = ?") where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
} else {
where = append(where, "`order_cash`.cash_activity_id = ?")
} }
args = append(args, s) args = append(args, s)
} }
@ -325,10 +243,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
if v, ok := req.Filters["order_voucher_channel_activity_id_eq"]; ok { if v, ok := req.Filters["order_voucher_channel_activity_id_eq"]; ok {
s := toString(v) s := toString(v)
if s != "" { if s != "" {
if req.MainTable == "order_info" { if tbl, col, ok := sch.FilterColumn("order_voucher_channel_activity_id_eq"); ok {
where = append(where, "`order_voucher`.channel_batch_no = ?") where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
} else {
where = append(where, "`order_voucher`.channel_activity_id = ?")
} }
args = append(args, s) args = append(args, s)
} }
@ -336,8 +252,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
if v, ok := req.Filters["voucher_batch_channel_activity_id_eq"]; ok { if v, ok := req.Filters["voucher_batch_channel_activity_id_eq"]; ok {
s := toString(v) s := toString(v)
if s != "" { if s != "" {
if req.MainTable == "order" { // only marketing schema has voucher_batch if tbl, col, ok := sch.FilterColumn("voucher_batch_channel_activity_id_eq"); ok {
where = append(where, "`voucher_batch`.channel_activity_id = ?") where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
args = append(args, s) args = append(args, s)
} }
} }
@ -345,8 +261,8 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
if v, ok := req.Filters["merchant_out_biz_no_eq"]; ok { if v, ok := req.Filters["merchant_out_biz_no_eq"]; ok {
s := toString(v) s := toString(v)
if s != "" { if s != "" {
if req.MainTable == "order" { // marketing only if tbl, col, ok := sch.FilterColumn("merchant_out_biz_no_eq"); ok {
where = append(where, "`merchant_key_send`.out_biz_no = ?") where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
} }
args = append(args, s) args = append(args, s)
} }

View File

@ -0,0 +1,60 @@
package schema
type marketingSchema struct{}
func (marketingSchema) TableName(t string) string { return t }
func (marketingSchema) MapField(t, f string) (string, bool) { return f, true }
func (marketingSchema) BuildJoins(need map[string]bool, main string) []string {
out := []string{}
if need["order_detail"] {
out = append(out, " LEFT JOIN `order_detail` ON `order_detail`.order_number = `order`.order_number")
}
if need["order_cash"] {
out = append(out, " LEFT JOIN `order_cash` ON `order_cash`.order_number = `order`.order_number")
}
if need["order_voucher"] {
out = append(out, " LEFT JOIN `order_voucher` ON `order_voucher`.order_number = `order`.order_number")
}
if need["plan"] || need["key_batch"] {
out = append(out, " LEFT JOIN `plan` ON `plan`.id = `order`.plan_id")
}
if need["key_batch"] {
out = append(out, " LEFT JOIN `key_batch` ON `key_batch`.plan_id = `plan`.id")
}
if need["code_batch"] {
out = append(out, " LEFT JOIN `code_batch` ON `code_batch`.key_batch_id = `key_batch`.id")
}
if need["voucher"] {
out = append(out, " LEFT JOIN `voucher` ON `voucher`.channel_activity_id = `order_voucher`.channel_activity_id")
}
if need["voucher_batch"] {
out = append(out, " LEFT JOIN `voucher_batch` ON `voucher_batch`.voucher_id = `voucher`.id")
}
if need["merchant_key_send"] {
out = append(out, " LEFT JOIN `merchant_key_send` ON `order`.`key` = `merchant_key_send`.key")
}
return out
}
func (marketingSchema) FilterColumn(key string) (string, string, bool) {
switch key {
case "creator_in": return "order", "creator", true
case "create_time_between": return "order", "create_time", true
case "type_eq": return "order", "type", true
case "out_trade_no_eq": return "order", "out_trade_no", true
case "account_eq": return "order", "account", true
case "plan_id_eq": return "order", "plan_id", true
case "key_batch_id_eq": return "order", "key_batch_id", true
case "product_id_eq": return "order", "product_id", true
case "reseller_id_eq": return "order", "reseller_id", true
case "code_batch_id_eq": return "order", "code_batch_id", true
case "order_cash_cash_activity_id_eq": return "order_cash", "cash_activity_id", true
case "order_voucher_channel_activity_id_eq": return "order_voucher", "channel_activity_id", true
case "voucher_batch_channel_activity_id_eq": return "voucher_batch", "channel_activity_id", true
case "merchant_out_biz_no_eq": return "merchant_key_send", "out_biz_no", true
default:
return "", "", false
}
}

View File

@ -0,0 +1,15 @@
package schema
type Schema interface {
TableName(string) string
MapField(string, string) (string, bool)
BuildJoins(map[string]bool, string) []string
FilterColumn(string) (string, string, bool)
}
func Get(datasource string, main string) Schema {
if datasource == "ymt" || main == "order_info" {
return ymtSchema{}
}
return marketingSchema{}
}

View File

@ -0,0 +1,74 @@
package schema
type ymtSchema struct{}
func (ymtSchema) TableName(t string) string {
if t == "order" {
return "order_info"
}
return t
}
func (s ymtSchema) MapField(t, f string) (string, bool) {
if t == "order" {
switch f {
case "order_number": return "order_no", true
case "key": return "key_code", true
case "creator": return "user_id", true
case "out_trade_no": return "out_order_no", true
case "plan_id": return "activity_id", true
case "reseller_id": return "merchant_id", true
case "product_id": return "goods_id", true
case "pay_amount": return "pay_price", true
case "key_batch_id": return "key_batch_name", true
default:
return f, true
}
}
return f, true
}
func (s ymtSchema) BuildJoins(need map[string]bool, main string) []string {
out := []string{}
if need["order_cash"] {
out = append(out, " LEFT JOIN `order_cash` ON `order_cash`.order_no = `order_info`.order_no")
}
if need["order_voucher"] {
out = append(out, " LEFT JOIN `order_voucher` ON `order_voucher`.order_no = `order_info`.order_no")
}
if need["order_digit"] {
out = append(out, " LEFT JOIN `order_digit` ON `order_digit`.order_no = `order_info`.order_no")
}
if need["goods_voucher_batch"] {
out = append(out, " LEFT JOIN `goods_voucher_batch` ON `goods_voucher_batch`.channel_batch_no = `order_voucher`.channel_batch_no")
}
if need["goods_voucher_subject_config"] {
out = append(out, " LEFT JOIN `goods_voucher_subject_config` ON `goods_voucher_subject_config`.id = `goods_voucher_batch`.voucher_subject_id")
}
if need["merchant"] {
out = append(out, " LEFT JOIN `merchant` ON `merchant`.id = `order_info`.merchant_id")
}
if need["activity"] {
out = append(out, " LEFT JOIN `activity` ON `activity`.id = `order_info`.activity_id")
}
return out
}
func (s ymtSchema) FilterColumn(key string) (string, string, bool) {
switch key {
case "creator_in": return "order", "user_id", true
case "create_time_between": return "order", "create_time", true
case "type_eq": return "order", "type", true
case "out_trade_no_eq": return "order", "out_order_no", true
case "account_eq": return "order", "account", true
case "plan_id_eq": return "order", "activity_id", true
case "key_batch_id_eq": return "order", "key_batch_name", true
case "product_id_eq": return "order", "goods_id", true
case "reseller_id_eq": return "order", "merchant_id", true
case "code_batch_id_eq": return "order", "supplier_product_id", true
case "order_cash_cash_activity_id_eq": return "order_cash", "activity_id", true
case "order_voucher_channel_activity_id_eq": return "order_voucher", "channel_batch_no", true
default:
return "", "", false
}
}

View File

@ -68,3 +68,8 @@ trace_id=a89d208e57f34d306f9315a6fcce39f7 sql=INSERT INTO export_templates (name
{"bytes":1272,"duration_ms":170,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T11:31:14+08:00"} {"bytes":1272,"duration_ms":170,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T11:31:14+08:00"}
{"bytes":870,"duration_ms":99,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T11:31:19+08:00"} {"bytes":870,"duration_ms":99,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T11:31:19+08:00"}
{"bytes":1614,"duration_ms":11,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T11:31:19+08:00"} {"bytes":1614,"duration_ms":11,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T11:31:19+08:00"}
connecting YMT MySQL: 47.97.27.195:3306 db merketing user root
connecting Marketing MySQL: 192.168.6.92:3306 db market user root
server listening on :8077
{"bytes":1371,"duration_ms":56,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T14:19:10+08:00"}
{"bytes":870,"duration_ms":131,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T14:20:15+08:00"}