Compare commits

...

2 Commits

Author SHA1 Message Date
zhouyonggao 7098b99046 feat(ymt): 新增易码通用户、商户和活动API接口及前端筛选功能
重构creators.go以支持易码通数据源查询
在router.go中添加易码通相关路由
新增ymt_users.go、ymt_merchants.go和ymt_activities.go处理易码通数据
修改前端界面增加易码通专属筛选条件
调整SQL构建器以适配易码通字段映射
优化导出功能支持易码通数据源
2025-11-28 13:40:12 +08:00
zhouyonggao b7c194be4b feat(config): 添加环境变量加载功能
新增 LoadEnv 函数用于从 .env.local 文件加载环境变量
支持从多个路径查找配置文件
2025-11-27 18:40:57 +08:00
23 changed files with 551 additions and 79 deletions

BIN
server/bin/marketing-data-server Executable file

Binary file not shown.

View File

@ -30,15 +30,14 @@ func (a *CreatorsAPI) list(w http.ResponseWriter, r *http.Request) {
if limitStr != "" {
if n, err := strconv.Atoi(limitStr); err == nil && n > 0 && n <= 10000 { limit = n }
}
// Try plan table first (creator, creator_name)
sql1 := "SELECT DISTINCT creator, COALESCE(creator_name, '') AS name FROM plan WHERE creator IS NOT NULL"
sql1 := "SELECT DISTINCT user_id, COALESCE(user_name, '') AS name FROM activity WHERE user_id IS NOT NULL"
args := []interface{}{}
if q != "" {
sql1 += " AND (CAST(creator AS CHAR) LIKE ? OR creator_name LIKE ?)"
sql1 += " AND (CAST(user_id AS CHAR) LIKE ? OR user_name LIKE ?)"
like := "%" + q + "%"
args = append(args, like, like)
}
sql1 += " ORDER BY creator ASC LIMIT ?"
sql1 += " ORDER BY user_id ASC LIMIT ?"
args = append(args, limit)
rows, err := a.marketing.Query(sql1, args...)
out := []map[string]interface{}{}
@ -53,9 +52,31 @@ func (a *CreatorsAPI) list(w http.ResponseWriter, r *http.Request) {
out = append(out, m)
}
}
// Fallback to order table if empty or error
if err != nil || len(out) == 0 {
sql2 := "SELECT DISTINCT creator, '' AS name FROM `order` WHERE creator IS NOT NULL"
sqlPlan := "SELECT DISTINCT creator, COALESCE(creator_name, '') AS name FROM plan WHERE creator IS NOT NULL"
argsPlan := []interface{}{}
if q != "" {
sqlPlan += " AND (CAST(creator AS CHAR) LIKE ? OR creator_name LIKE ?)"
like := "%" + q + "%"
argsPlan = append(argsPlan, like, like)
}
sqlPlan += " ORDER BY creator ASC LIMIT ?"
argsPlan = append(argsPlan, limit)
rowsPlan, errPlan := a.marketing.Query(sqlPlan, argsPlan...)
if errPlan == nil {
defer rowsPlan.Close()
tmp := []map[string]interface{}{}
for rowsPlan.Next() {
var id sql.NullInt64
var name sql.NullString
if err := rowsPlan.Scan(&id, &name); err != nil { continue }
if !id.Valid { continue }
tmp = append(tmp, map[string]interface{}{"id": id.Int64, "name": name.String})
}
if len(tmp) > 0 { out = tmp }
}
if len(out) == 0 {
sql2 := "SELECT DISTINCT creator, '' AS name FROM `order` WHERE creator IS NOT NULL"
args2 := []interface{}{}
if q != "" {
sql2 += " AND CAST(creator AS CHAR) LIKE ?"
@ -64,19 +85,20 @@ func (a *CreatorsAPI) list(w http.ResponseWriter, r *http.Request) {
sql2 += " ORDER BY creator ASC LIMIT ?"
args2 = append(args2, limit)
rows2, err2 := a.marketing.Query(sql2, args2...)
if err2 != nil {
fail(w, r, http.StatusInternalServerError, err2.Error())
return
}
defer rows2.Close()
out = out[:0]
for rows2.Next() {
var id sql.NullInt64
var name sql.NullString
if err := rows2.Scan(&id, &name); err != nil { continue }
if !id.Valid { continue }
m := map[string]interface{}{"id": id.Int64, "name": name.String}
out = append(out, m)
if err2 != nil {
fail(w, r, http.StatusInternalServerError, err2.Error())
return
}
defer rows2.Close()
out = out[:0]
for rows2.Next() {
var id sql.NullInt64
var name sql.NullString
if err := rows2.Scan(&id, &name); err != nil { continue }
if !id.Valid { continue }
m := map[string]interface{}{"id": id.Int64, "name": name.String}
out = append(out, m)
}
}
}
ok(w, r, out)

View File

@ -106,31 +106,31 @@ func (a *ExportsAPI) create(w http.ResponseWriter, r *http.Request) {
}
r = WithSQL(r, q)
dataDB := a.selectDataDB(ds)
score, sugg, err := exporter.EvaluateExplain(dataDB, q, args)
if err != nil {
fail(w, r, http.StatusBadRequest, err.Error())
return
}
sugg = append(sugg, exporter.IndexSuggestions(req)...)
score, sugg, err := exporter.EvaluateExplain(dataDB, q, args)
if err != nil {
fail(w, r, http.StatusBadRequest, err.Error())
return
}
sugg = append(sugg, exporter.IndexSuggestions(req)...)
const passThreshold = 60
if score < passThreshold {
fail(w, r, http.StatusBadRequest, fmt.Sprintf("EXPLAIN 未通过:评分=%d请优化索引或缩小查询范围", score))
return
}
var estimate int64
func() {
idx := strings.Index(q, " FROM ")
if idx > 0 {
cq := "SELECT COUNT(1)" + q[idx:]
row := dataDB.QueryRow(cq, args...)
var cnt int64
if err := row.Scan(&cnt); err == nil {
estimate = cnt
return
}
}
estimate = 0
}()
var estimate int64
func() {
idx := strings.Index(q, " FROM ")
if idx > 0 {
cq := "SELECT COUNT(1)" + q[idx:]
row := dataDB.QueryRow(cq, args...)
var cnt int64
if err := row.Scan(&cnt); err == nil {
estimate = cnt
return
}
}
estimate = 0
}()
labels := FieldLabels()
hdrs := make([]string, len(fs))
for i, tf := range fs {
@ -148,7 +148,7 @@ func (a *ExportsAPI) create(w http.ResponseWriter, r *http.Request) {
}
}
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(map[string]interface{}{"sql": q, "suggestions": sugg}), 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(map[string]interface{}{"sql": q, "suggestions": sugg}), score, estimate, p.FileFormat, time.Now(), time.Now()}
log.Printf("trace_id=%s sql=%s args=%v", TraceIDFrom(r), ejSQL, ejArgs)
res, err := a.meta.Exec(ejSQL, ejArgs...)
if err != nil {
@ -225,7 +225,7 @@ func (a *ExportsAPI) runJob(id uint64, db *sql.DB, q string, args []interface{},
fetched = true
if err := rows2.Scan(dest...); err != nil {
rows2.Close()
a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id?", "failed", time.Now(), id)
a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id= ?", "failed", time.Now(), id)
return
}
vals := make([]string, len(cols))
@ -312,7 +312,7 @@ func (a *ExportsAPI) runJob(id uint64, db *sql.DB, q string, args []interface{},
fetched = true
if err := rows3.Scan(dest...); err != nil {
rows3.Close()
a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id?", "failed", time.Now(), id)
a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id= ?", "failed", time.Now(), id)
return
}
vals := make([]string, len(cols))
@ -556,7 +556,7 @@ func (a *ExportsAPI) runJob(id uint64, db *sql.DB, q string, args []interface{},
fetched = true
if err := rows2.Scan(dest...); err != nil {
rows2.Close()
a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id?", "failed", time.Now(), id)
a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id= ?", "failed", time.Now(), id)
return
}
vals := make([]string, len(cols))
@ -609,7 +609,7 @@ func (a *ExportsAPI) runJob(id uint64, db *sql.DB, q string, args []interface{},
var tick2 int64
for rows.Next() {
if err := rows.Scan(dest2...); err != nil {
a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id?", "failed", time.Now(), id)
a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id= ?", "failed", time.Now(), id)
return
}
vals := make([]string, len(cols))
@ -794,11 +794,11 @@ func (a *ExportsAPI) getSQL(w http.ResponseWriter, r *http.Request, id string) {
json.Unmarshal(filters, &fl)
wl := Whitelist()
req := exporter.BuildRequest{MainTable: main, Datasource: ds, Fields: fs, Filters: fl}
q, args, err := exporter.BuildSQL(req, wl)
if err != nil {
failCat(w, r, http.StatusBadRequest, err.Error(), "sql_build_error")
return
}
q, args, err := exporter.BuildSQL(req, wl)
if err != nil {
failCat(w, r, http.StatusBadRequest, err.Error(), "sql_build_error")
return
}
formatArg := func(a interface{}) string {
switch t := a.(type) {
case nil:
@ -975,10 +975,10 @@ func (a *ExportsAPI) list(w http.ResponseWriter, r *http.Request) {
rows, err = a.meta.Query("SELECT id, template_id, status, requested_by, row_estimate, total_rows, file_format, created_at, updated_at, explain_score, explain_json FROM export_jobs ORDER BY id DESC LIMIT ? OFFSET ?", size, offset)
}
}
if err != nil {
failCat(w, r, http.StatusInternalServerError, err.Error(), "explain_error")
return
}
if err != nil {
failCat(w, r, http.StatusInternalServerError, err.Error(), "explain_error")
return
}
defer rows.Close()
items := []map[string]interface{}{}
for rows.Next() {

View File

@ -19,6 +19,12 @@ func NewRouter(metaDB *sql.DB, marketingDB *sql.DB) http.Handler {
mux.Handle("/api/resellers/", withAccess(withTrace(ResellersHandler(marketingDB))))
mux.Handle("/api/plans", withAccess(withTrace(PlansHandler(marketingDB))))
mux.Handle("/api/plans/", withAccess(withTrace(PlansHandler(marketingDB))))
mux.Handle("/api/ymt/users", withAccess(withTrace(YMTUsersHandler(metaDB))))
mux.Handle("/api/ymt/users/", withAccess(withTrace(YMTUsersHandler(metaDB))))
mux.Handle("/api/ymt/merchants", withAccess(withTrace(YMTMerchantsHandler(metaDB))))
mux.Handle("/api/ymt/merchants/", withAccess(withTrace(YMTMerchantsHandler(metaDB))))
mux.Handle("/api/ymt/activities", withAccess(withTrace(YMTActivitiesHandler(metaDB))))
mux.Handle("/api/ymt/activities/", withAccess(withTrace(YMTActivitiesHandler(metaDB))))
mux.HandleFunc("/api/utils/decode_key", func(w http.ResponseWriter, r *http.Request) {
v := r.URL.Query().Get("v")
if v == "" {

View File

@ -0,0 +1,62 @@
package api
import (
"database/sql"
"net/http"
"strconv"
)
type YMTActivitiesAPI struct {
ymt *sql.DB
}
func YMTActivitiesHandler(ymt *sql.DB) http.Handler {
api := &YMTActivitiesAPI{ymt: ymt}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
api.list(w, r)
return
}
w.WriteHeader(http.StatusNotFound)
})
}
func (a *YMTActivitiesAPI) list(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
merchantIDStr := q.Get("merchant_id")
like := q.Get("q")
limitStr := q.Get("limit")
limit := 2000
if limitStr != "" {
if n, err := strconv.Atoi(limitStr); err == nil && n > 0 && n <= 10000 { limit = n }
}
sql1 := "SELECT id, name FROM activity WHERE id IS NOT NULL"
args := []interface{}{}
if merchantIDStr != "" {
sql1 += " AND merchant_id = ?"
args = append(args, merchantIDStr)
}
if like != "" {
sql1 += " AND (CAST(id AS CHAR) LIKE ? OR name LIKE ?)"
s := "%" + like + "%"
args = append(args, s, s)
}
sql1 += " ORDER BY id ASC LIMIT ?"
args = append(args, limit)
rows, err := a.ymt.Query(sql1, args...)
if err != nil {
fail(w, r, http.StatusInternalServerError, err.Error())
return
}
defer rows.Close()
out := []map[string]interface{}{}
for rows.Next() {
var id sql.NullInt64
var name sql.NullString
if err := rows.Scan(&id, &name); err != nil { continue }
if !id.Valid { continue }
out = append(out, map[string]interface{}{"id": id.Int64, "name": name.String})
}
ok(w, r, out)
}

View File

@ -0,0 +1,62 @@
package api
import (
"database/sql"
"net/http"
"strconv"
)
type YMTMerchantsAPI struct {
ymt *sql.DB
}
func YMTMerchantsHandler(ymt *sql.DB) http.Handler {
api := &YMTMerchantsAPI{ymt: ymt}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
api.list(w, r)
return
}
w.WriteHeader(http.StatusNotFound)
})
}
func (a *YMTMerchantsAPI) list(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
userIDStr := q.Get("user_id")
like := q.Get("q")
limitStr := q.Get("limit")
limit := 2000
if limitStr != "" {
if n, err := strconv.Atoi(limitStr); err == nil && n > 0 && n <= 10000 { limit = n }
}
sql1 := "SELECT id, name FROM merchant WHERE id IS NOT NULL"
args := []interface{}{}
if userIDStr != "" {
sql1 += " AND user_id = ?"
args = append(args, userIDStr)
}
if like != "" {
sql1 += " AND (CAST(id AS CHAR) LIKE ? OR name LIKE ?)"
s := "%" + like + "%"
args = append(args, s, s)
}
sql1 += " ORDER BY id ASC LIMIT ?"
args = append(args, limit)
rows, err := a.ymt.Query(sql1, args...)
if err != nil {
fail(w, r, http.StatusInternalServerError, err.Error())
return
}
defer rows.Close()
out := []map[string]interface{}{}
for rows.Next() {
var id sql.NullInt64
var name sql.NullString
if err := rows.Scan(&id, &name); err != nil { continue }
if !id.Valid { continue }
out = append(out, map[string]interface{}{"id": id.Int64, "name": name.String})
}
ok(w, r, out)
}

View File

@ -0,0 +1,57 @@
package api
import (
"database/sql"
"net/http"
"strconv"
"strings"
)
type YMTUsersAPI struct {
ymt *sql.DB
}
func YMTUsersHandler(ymt *sql.DB) http.Handler {
api := &YMTUsersAPI{ymt: ymt}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
p := strings.TrimPrefix(r.URL.Path, "/api/ymt/users")
if r.Method == http.MethodGet && p == "" {
api.list(w, r)
return
}
w.WriteHeader(http.StatusNotFound)
})
}
func (a *YMTUsersAPI) list(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("q")
limitStr := r.URL.Query().Get("limit")
limit := 2000
if limitStr != "" {
if n, err := strconv.Atoi(limitStr); err == nil && n > 0 && n <= 10000 { limit = n }
}
sql1 := "SELECT DISTINCT user_id, COALESCE(user_name, '') AS name FROM activity WHERE user_id IS NOT NULL"
args := []interface{}{}
if q != "" {
sql1 += " AND (CAST(user_id AS CHAR) LIKE ? OR user_name LIKE ?)"
like := "%" + q + "%"
args = append(args, like, like)
}
sql1 += " ORDER BY user_id ASC LIMIT ?"
args = append(args, limit)
rows, err := a.ymt.Query(sql1, args...)
if err != nil {
fail(w, r, http.StatusInternalServerError, err.Error())
return
}
defer rows.Close()
out := []map[string]interface{}{}
for rows.Next() {
var id sql.NullInt64
var name sql.NullString
if err := rows.Scan(&id, &name); err != nil { continue }
if !id.Valid { continue }
out = append(out, map[string]interface{}{"id": id.Int64, "name": name.String})
}
ok(w, r, out)
}

View File

@ -101,3 +101,38 @@ func (d DB) DSN() string {
}
return d.User + ":" + d.Password + "@tcp(" + d.Host + ":" + d.Port + ")/" + d.Name + "?parseTime=True&loc=Local&charset=utf8mb4"
}
func LoadEnv() {
paths := []string{
".env.local",
filepath.Join("server", ".env.local"),
}
for _, p := range paths {
f, err := os.Open(p)
if err != nil {
continue
}
b, err := io.ReadAll(f)
f.Close()
if err != nil {
continue
}
lines := strings.Split(string(b), "\n")
for _, ln := range lines {
s := strings.TrimSpace(ln)
if s == "" || strings.HasPrefix(s, "#") {
continue
}
kv := strings.SplitN(s, "=", 2)
if len(kv) != 2 {
continue
}
k := strings.TrimSpace(kv[0])
v := strings.TrimSpace(kv[1])
if k != "" {
os.Setenv(k, v)
}
}
break
}
}

View File

@ -32,12 +32,14 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
return "", nil, errors.New("invalid field format")
}
t, f := parts[0], parts[1]
if req.Datasource == "ymt" && t == "order_voucher" && f == "channel_activity_id" {
f = "channel_batch_no"
}
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
}

View File

@ -25,6 +25,13 @@ func (s ymtSchema) MapField(t, f string) (string, bool) {
return f, true
}
}
if t == "order_voucher" {
switch f {
case "channel_activity_id": return "channel_batch_no", true
default:
return f, true
}
}
return f, true
}

View File

@ -138,3 +138,154 @@ server listening on :8077
{"bytes":2075,"duration_ms":44,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:38:02+08:00"}
{"bytes":2075,"duration_ms":47,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:38:04+08:00"}
{"bytes":2187,"duration_ms":92,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:38:10+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":2075,"duration_ms":55,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:03+08:00"}
{"bytes":19367,"duration_ms":81,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:03+08:00"}
{"bytes":884,"duration_ms":94,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:05+08:00"}
{"bytes":19423,"duration_ms":86,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:05+08:00"}
{"bytes":19423,"duration_ms":79,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:05+08:00"}
{"bytes":2075,"duration_ms":49,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:08+08:00"}
{"bytes":19367,"duration_ms":70,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:08+08:00"}
{"bytes":2075,"duration_ms":48,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:13+08:00"}
{"bytes":19367,"duration_ms":103,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:13+08:00"}
{"bytes":884,"duration_ms":100,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:15+08:00"}
{"bytes":19423,"duration_ms":79,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:15+08:00"}
{"bytes":19423,"duration_ms":87,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:15+08:00"}
{"bytes":2187,"duration_ms":98,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:21+08:00"}
{"bytes":14655,"duration_ms":785,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:22+08:00"}
{"bytes":14655,"duration_ms":1165,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:22+08:00"}
{"bytes":2187,"duration_ms":105,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:30+08:00"}
{"bytes":14655,"duration_ms":810,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:31+08:00"}
{"bytes":14655,"duration_ms":798,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:32+08:00"}
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate request body: {"name":"易码通-红包订单-导出","visibility":"private","file_format":"xlsx","fields":["order.order_number","order.creator","order.out_trade_no","order.type","order.status","order.contract_price","order.num","order.pay_amount","order.create_time"],"filters":{"type_eq":3},"main_table":"order_info"}
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate parsed payload: map[fields:[order.order_number order.creator order.out_trade_no order.type order.status order.contract_price order.num order.pay_amount order.create_time] file_format:xlsx filters:map[type_eq:3] main_table:order_info name:易码通-红包订单-导出 visibility:private]
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate template ID: 19
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate processing field: main_table, value: order_info, type: string
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate added string field: main_table, value: order_info
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate processing field: name, value: 易码通-红包订单-导出, type: string
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate added string field: name, value: 易码通-红包订单-导出
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate processing field: visibility, value: private, type: string
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate added string field: visibility, value: private
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate processing field: file_format, value: xlsx, type: string
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate added string field: file_format, value: xlsx
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate processing field: fields, value: [order.order_number order.creator order.out_trade_no order.type order.status order.contract_price order.num order.pay_amount order.create_time], type: []interface {}
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate added fields_json: ["order.order_number","order.creator","order.out_trade_no","order.type","order.status","order.contract_price","order.num","order.pay_amount","order.create_time"]
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate processing field: filters, value: map[type_eq:3], type: map[string]interface {}
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate added filters_json: {"type_eq":3}
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate executing SQL: UPDATE export_templates SET main_table=?,name=?,visibility=?,file_format=?,fields_json=?,filters_json=?,updated_at=? WHERE id= ?
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate SQL args: [order_info 易码通-红包订单-导出 private xlsx [91 34 111 114 100 101 114 46 111 114 100 101 114 95 110 117 109 98 101 114 34 44 34 111 114 100 101 114 46 99 114 101 97 116 111 114 34 44 34 111 114 100 101 114 46 111 117 116 95 116 114 97 100 101 95 110 111 34 44 34 111 114 100 101 114 46 116 121 112 101 34 44 34 111 114 100 101 114 46 115 116 97 116 117 115 34 44 34 111 114 100 101 114 46 99 111 110 116 114 97 99 116 95 112 114 105 99 101 34 44 34 111 114 100 101 114 46 110 117 109 34 44 34 111 114 100 101 114 46 112 97 121 95 97 109 111 117 110 116 34 44 34 111 114 100 101 114 46 99 114 101 97 116 101 95 116 105 109 101 34 93] [123 34 116 121 112 101 95 101 113 34 58 51 125] 2025-11-27 18:41:40.910221 +0800 CST m=+46.878715918 19]
trace_id=6f0f7bcc51739fdf6827f84eb1bfa1ba patchTemplate update successful
{"bytes":79,"duration_ms":110,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:41+08:00"}
{"bytes":2074,"duration_ms":50,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:41+08:00"}
{"bytes":583,"duration_ms":101,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:45+08:00"}
{"bytes":14655,"duration_ms":797,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-27T18:41:46+08:00"}
{"bytes":19367,"duration_ms":136,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:08:37+08:00"}
{"bytes":2074,"duration_ms":411,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:08:38+08:00"}
{"bytes":884,"duration_ms":98,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:08:46+08:00"}
{"bytes":1614,"duration_ms":14,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:08:46+08:00"}
{"bytes":2074,"duration_ms":100,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:09:02+08:00"}
{"bytes":19367,"duration_ms":105,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:09:02+08:00"}
{"bytes":884,"duration_ms":165,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:09:05+08:00"}
{"bytes":1614,"duration_ms":33,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:09:05+08:00"}
{"bytes":925,"duration_ms":64,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:09:05+08:00"}
{"bytes":2074,"duration_ms":239,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:10:47+08:00"}
{"bytes":19367,"duration_ms":472,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:10:48+08:00"}
{"bytes":884,"duration_ms":134,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:10:49+08:00"}
{"bytes":925,"duration_ms":44,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:10:49+08:00"}
{"bytes":1614,"duration_ms":44,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:10:49+08:00"}
{"bytes":1221,"duration_ms":97,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:10:55+08:00"}
{"bytes":1614,"duration_ms":13,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:10:55+08:00"}
{"bytes":925,"duration_ms":14,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:10:55+08:00"}
{"bytes":2074,"duration_ms":158,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:12:21+08:00"}
{"bytes":19367,"duration_ms":293,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:12:21+08:00"}
{"bytes":1221,"duration_ms":138,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:12:22+08:00"}
{"bytes":925,"duration_ms":9,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:12:22+08:00"}
{"bytes":1614,"duration_ms":19,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:12:22+08:00"}
{"bytes":928,"duration_ms":96,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:12:37+08:00"}
{"bytes":1614,"duration_ms":12,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:12:37+08:00"}
{"bytes":925,"duration_ms":12,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:12:37+08:00"}
{"bytes":583,"duration_ms":103,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:12:44+08:00"}
{"bytes":925,"duration_ms":17,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:12:44+08:00"}
{"bytes":1086,"duration_ms":145,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:12:47+08:00"}
{"bytes":925,"duration_ms":48,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:12:47+08:00"}
{"bytes":1272,"duration_ms":106,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:12:49+08:00"}
{"bytes":925,"duration_ms":11,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:12:49+08:00"}
{"bytes":1221,"duration_ms":104,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:13:04+08:00"}
{"bytes":1614,"duration_ms":9,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:13:04+08:00"}
{"bytes":925,"duration_ms":15,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:13:04+08:00"}
{"bytes":2074,"duration_ms":98,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:13:08+08:00"}
{"bytes":19367,"duration_ms":100,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:13:08+08:00"}
{"bytes":1221,"duration_ms":103,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:13:10+08:00"}
{"bytes":925,"duration_ms":18,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:13:10+08:00"}
{"bytes":1614,"duration_ms":18,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:13:10+08:00"}
{"bytes":121,"duration_ms":255,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:13:27+08:00"}
{"bytes":121,"duration_ms":248,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:13:28+08:00"}
{"bytes":583,"duration_ms":99,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:13:32+08:00"}
{"bytes":925,"duration_ms":10,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:13:32+08:00"}
{"bytes":1086,"duration_ms":96,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:13:39+08:00"}
{"bytes":925,"duration_ms":9,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:13:39+08:00"}
{"bytes":19367,"duration_ms":78,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:18+08:00"}
{"bytes":2074,"duration_ms":95,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:18+08:00"}
{"bytes":1086,"duration_ms":102,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:21+08:00"}
{"bytes":925,"duration_ms":16,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:21+08:00"}
{"bytes":1272,"duration_ms":97,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:24+08:00"}
{"bytes":925,"duration_ms":8,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:24+08:00"}
{"bytes":583,"duration_ms":98,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:41+08:00"}
{"bytes":925,"duration_ms":11,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:41+08:00"}
{"bytes":14631,"duration_ms":793,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:52+08:00"}
{"bytes":14631,"duration_ms":835,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:53+08:00"}
{"bytes":884,"duration_ms":99,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:54+08:00"}
{"bytes":1614,"duration_ms":9,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:54+08:00"}
{"bytes":925,"duration_ms":7,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:54+08:00"}
{"bytes":2074,"duration_ms":110,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:59+08:00"}
{"bytes":19367,"duration_ms":122,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:18:59+08:00"}
{"bytes":2074,"duration_ms":105,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:29:57+08:00"}
{"bytes":19367,"duration_ms":133,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:29:57+08:00"}
{"bytes":583,"duration_ms":98,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:29:59+08:00"}
{"bytes":925,"duration_ms":11,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:29:59+08:00"}
{"bytes":884,"duration_ms":162,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:30:09+08:00"}
{"bytes":925,"duration_ms":18,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:30:09+08:00"}
{"bytes":1614,"duration_ms":19,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:30:09+08:00"}
{"bytes":583,"duration_ms":133,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:30:11+08:00"}
{"bytes":925,"duration_ms":44,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:30:11+08:00"}
{"bytes":1086,"duration_ms":110,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:30:13+08:00"}
{"bytes":925,"duration_ms":54,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:30:13+08:00"}
{"bytes":583,"duration_ms":104,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:30:46+08:00"}
{"bytes":925,"duration_ms":15,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:30:46+08:00"}
{"bytes":583,"duration_ms":124,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:30:56+08:00"}
{"bytes":925,"duration_ms":27,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:30:56+08:00"}
{"bytes":2074,"duration_ms":111,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:32:23+08:00"}
{"bytes":19367,"duration_ms":228,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:32:23+08:00"}
{"bytes":1086,"duration_ms":109,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:32:26+08:00"}
{"bytes":925,"duration_ms":22,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:32:26+08:00"}
{"bytes":1086,"duration_ms":119,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:32:35+08:00"}
{"bytes":925,"duration_ms":9,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:32:35+08:00"}
{"bytes":2074,"duration_ms":106,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:32:38+08:00"}
{"bytes":19367,"duration_ms":210,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:32:39+08:00"}
{"bytes":1221,"duration_ms":102,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:33:02+08:00"}
{"bytes":1614,"duration_ms":38,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:33:02+08:00"}
{"bytes":925,"duration_ms":39,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:33:02+08:00"}
{"bytes":583,"duration_ms":106,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:33:31+08:00"}
{"bytes":925,"duration_ms":8,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:33:31+08:00"}
{"bytes":19367,"duration_ms":80,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:33:34+08:00"}
{"bytes":2074,"duration_ms":101,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:33:34+08:00"}
{"bytes":1086,"duration_ms":102,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:33:35+08:00"}
{"bytes":925,"duration_ms":12,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:33:35+08:00"}
{"bytes":884,"duration_ms":106,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:33:47+08:00"}
{"bytes":925,"duration_ms":23,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:33:47+08:00"}
{"bytes":1614,"duration_ms":23,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:33:47+08:00"}
{"bytes":583,"duration_ms":105,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:33:50+08:00"}
{"bytes":925,"duration_ms":24,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:33:50+08:00"}
{"bytes":1086,"duration_ms":119,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:35:35+08:00"}
{"bytes":925,"duration_ms":18,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:35:35+08:00"}
{"bytes":2074,"duration_ms":114,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:35:39+08:00"}
{"bytes":19367,"duration_ms":208,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:35:39+08:00"}
{"bytes":1086,"duration_ms":95,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:35:41+08:00"}
{"bytes":2074,"duration_ms":109,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:39:04+08:00"}
{"bytes":19367,"duration_ms":166,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:39:04+08:00"}
{"bytes":1086,"duration_ms":243,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:39:06+08:00"}
{"bytes":2074,"duration_ms":99,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:39:13+08:00"}
{"bytes":19367,"duration_ms":102,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:39:13+08:00"}
{"bytes":583,"duration_ms":100,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-28T09:39:15+08:00"}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
订单编号,KEY,支付流水号,订单类型,账号,合同单价,数量,总金额,支付金额,支付方式,支付状态,充值时间,创建时间,更新时间,计划标题,分销商名称,商品名称,官方价,成本价,批次名称,标题,订单状态,商户业务号,是否使用优惠券
1 订单编号 KEY 支付流水号 订单类型 账号 合同单价 数量 总金额 支付金额 支付方式 支付状态 充值时间 创建时间 更新时间 计划标题 分销商名称 商品名称 官方价 成本价 批次名称 标题 订单状态 商户业务号 是否使用优惠券

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -232,8 +232,30 @@
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">层级筛选</el-divider>
<el-row :gutter="8" v-if="isOrder">
<el-row :gutter="8" v-if="isOrder && exportForm.datasource==='ymt'">
<el-col :span="8">
<el-form-item label="创建者" prop="ymtCreatorId">
<el-select v-model.number="exportForm.ymtCreatorId" filterable :teleported="false" placeholder="请选择创建者" style="width:100%">
<el-option v-for="opt in ymtCreatorOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="客户" prop="ymtMerchantId">
<el-select v-model.number="exportForm.ymtMerchantId" :disabled="!exportForm.ymtCreatorId" filterable :teleported="false" placeholder="请选择客户" style="width:100%">
<el-option v-for="opt in ymtMerchantOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="活动" prop="ymtActivityId">
<el-select v-model.number="exportForm.ymtActivityId" :disabled="!exportForm.ymtMerchantId" filterable :teleported="false" placeholder="请选择活动" style="width:100%">
<el-option v-for="opt in ymtActivityOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="8" v-if="isOrder && exportForm.datasource==='marketing'">
<el-col :span="12">
<el-form-item label="创建者" prop="creator">
<el-select v-model="exportForm.creatorIds" multiple filterable :disabled="hasUserId" :teleported="false" placeholder="请选择创建者" style="width:100%">
@ -242,14 +264,14 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="分销商ID" prop="resellerId">
<el-form-item label="分销商" prop="resellerId">
<el-select v-model.number="exportForm.resellerId" filterable :teleported="false" placeholder="请选择分销商" style="width:100%" :disabled="!hasCreators">
<el-option v-for="opt in resellerOptions" :key="opt.value" :label="opt.label||String(opt.value)" :value="opt.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="8" v-if="isOrder">
<el-row :gutter="8" v-if="isOrder && exportForm.datasource==='marketing'">
<el-col :span="12">
<el-form-item label="计划" prop="planId">
<el-select v-model="exportForm.planId" :disabled="!hasReseller" :teleported="false" filterable placeholder="选择计划">
@ -261,19 +283,12 @@
<el-row :gutter="8" v-if="isOrder">
<el-col :span="12" v-if="exportType===3">
<el-form-item label="红包批次号" prop="cashActivityId"><el-input v-model="exportForm.cashActivityId" placeholder="order_cash.cash_activity_id" /></el-input></el-form-item>
</el-col>
<el-col :span="12" v-if="exportType===2">
<el-form-item label="立减金批次" prop="voucherChannelActivityId"><el-input v-model="exportForm.voucherChannelActivityId" placeholder="order_voucher.channel_activity_id" /></el-input></el-form-item>
</el-col>
</el-row>
<el-row :gutter="8" v-if="isOrder">
<el-col :span="12" v-if="exportType===2">
<el-form-item label="立减金批次(批次表)" prop="voucherBatchChannelActivityId"><el-input v-model="exportForm.voucherBatchChannelActivityId" placeholder="voucher_batch.channel_activity_id" /></el-input></el-form-item>
<el-form-item label="立减金批次号" prop="voucherChannelActivityId"><el-input v-model="exportForm.voucherChannelActivityId" placeholder="请输入立减金批次号" /></el-form-item>
</el-col>
</el-row>
<!-- 输出格式按模板设置,不在此显示 -->
</el-form>
<template #footer>

View File

@ -31,7 +31,7 @@ const { createApp, reactive } = Vue;
createWidth: (localStorage.getItem('tplDialogWidth') || '900px'),
editWidth: (localStorage.getItem('tplEditDialogWidth') || '900px'),
edit: { id: null, name: '', datasource: 'marketing', main_table: 'order', orderType: 1, fieldsSel: [], visibility: 'private', file_format: 'xlsx' },
exportForm: { tplId: null, datasource: 'marketing', file_format: 'xlsx', dateRange: [], creatorIds: [], creatorIdsRaw: '', resellerId: null, planId: null, keyBatchId: null, codeBatchId: null, productId: null, outTradeNo: '', account: '', cashActivityId: '', voucherChannelActivityId: '', voucherBatchChannelActivityId: '', outBizNo: '' },
exportForm: { tplId: null, datasource: 'marketing', file_format: 'xlsx', dateRange: [], creatorIds: [], creatorIdsRaw: '', resellerId: null, planId: null, keyBatchId: null, codeBatchId: null, productId: null, outTradeNo: '', account: '', voucherChannelActivityId: '', outBizNo: '', ymtCreatorId: '', ymtMerchantId: '', ymtActivityId: '' },
exportTpl: { id: null, filters: {}, main_table: '', fields: [], datasource: '', file_format: '' }
})
@ -233,7 +233,7 @@ const { createApp, reactive } = Vue;
],
order_voucher: [
{ value: 'channel', label: '渠道' },
{ value: 'channel_activity_id', label: '渠道立减金批次' },
{ value: 'channel_batch_no', label: '渠道立减金批次' },
{ value: 'channel_voucher_id', label: '渠道立减金ID' },
{ value: 'status', label: '状态' },
{ value: 'receive_mode', label: '领取方式' },
@ -820,8 +820,11 @@ const { createApp, reactive } = Vue;
return v || ''
}
const creatorOptions = Vue.ref([])
const ymtCreatorOptions = Vue.ref([])
const resellerOptions = Vue.ref([])
const planOptions = Vue.ref([])
const ymtMerchantOptions = Vue.ref([])
const ymtActivityOptions = Vue.ref([])
const hasCreators = Vue.computed(()=> Array.isArray(state.exportForm.creatorIds) && state.exportForm.creatorIds.length>0 )
const hasReseller = Vue.computed(()=> !!state.exportForm.resellerId)
const hasPlan = Vue.computed(()=> !!state.exportForm.planId)
@ -835,6 +838,14 @@ const { createApp, reactive } = Vue;
creatorOptions.value = arr.map(it=>({label: it.name || String(it.id), value: Number(it.id)}))
}catch(_e){ creatorOptions.value = [] }
}
const loadYmtCreators = async ()=>{
try{
const res = await fetch(API_BASE + '/api/ymt/users?limit=2000')
const data = await res.json()
const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : [])
ymtCreatorOptions.value = arr.map(it=>({ label: it.name || String(it.id), value: Number(it.id) }))
}catch(_e){ ymtCreatorOptions.value = [] }
}
const loadResellers = async ()=>{
const ids = Array.isArray(state.exportForm.creatorIds) ? state.exportForm.creatorIds : []
if(!ids.length){ resellerOptions.value = []; return }
@ -845,6 +856,32 @@ const { createApp, reactive } = Vue;
resellerOptions.value = arr.map(it=>({label: (it.name||'') + (it.name?'':'') , value: Number(it.id)}))
}catch(_e){ resellerOptions.value = [] }
}
const loadYmtMerchants = async ()=>{
const uid = state.exportForm.ymtCreatorId
if(!uid){ ymtMerchantOptions.value = []; return }
try{
const qs = new URLSearchParams()
qs.set('user_id', String(uid))
qs.set('limit', '2000')
const res = await fetch(API_BASE + '/api/ymt/merchants?' + qs.toString())
const data = await res.json()
const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : [])
ymtMerchantOptions.value = arr.map(it=>({ label: `${it.id} - ${it.name||''}`, value: Number(it.id) }))
}catch(_e){ ymtMerchantOptions.value = [] }
}
const loadYmtActivities = async ()=>{
const mid = state.exportForm.ymtMerchantId
if(!mid){ ymtActivityOptions.value = []; return }
try{
const qs = new URLSearchParams()
qs.set('merchant_id', String(mid))
qs.set('limit', '2000')
const res = await fetch(API_BASE + '/api/ymt/activities?' + qs.toString())
const data = await res.json()
const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : [])
ymtActivityOptions.value = arr.map(it=>({ label: `${it.id} - ${it.name||''}`, value: Number(it.id) }))
}catch(_e){ ymtActivityOptions.value = [] }
}
const loadPlans = async ()=>{
const rid = state.exportForm.resellerId
if(!rid){ planOptions.value = []; return }
@ -872,7 +909,8 @@ const { createApp, reactive } = Vue;
return []
})
const isOrder = Vue.computed(()=>{
return (state.exportTpl && state.exportTpl.main_table) === 'order'
const mt = state.exportTpl && state.exportTpl.main_table
return mt === 'order' || mt === 'order_info'
})
const orderTypeLabel = (n)=>{
if(n===1) return '直充卡密'
@ -881,7 +919,7 @@ const { createApp, reactive } = Vue;
return ''
}
const sceneLabel = (s)=>{
if(s==='order') return '订单'
if(s==='order' || s==='order_info') return '订单'
return s || ''
}
const exportTitle = Vue.computed(()=>{
@ -889,7 +927,7 @@ const { createApp, reactive } = Vue;
const mt = state.exportTpl && state.exportTpl.main_table
if(mt){
base += ' - ' + sceneLabel(mt)
if(mt==='order'){
if(mt==='order' || mt==='order_info'){
const list = exportTypeList.value
const labels = list.map(orderTypeLabel).filter(Boolean)
if(labels.length){
@ -1020,9 +1058,16 @@ const { createApp, reactive } = Vue;
await loadTemplateDetail(row.id)
state.exportForm.datasource = state.exportTpl.datasource || row.datasource || 'marketing'
state.exportForm.file_format = state.exportTpl.file_format || row.file_format || 'xlsx'
if(state.exportForm.datasource==='marketing'){ loadCreators() }
const uid = getUserId()
if(uid){ state.exportForm.creatorIds = [ Number(uid) ] }
if(state.exportForm.datasource==='marketing'){
loadCreators()
const uid = getUserId()
if(uid){ state.exportForm.creatorIds = [ Number(uid) ] }
}
if(state.exportForm.datasource==='ymt'){
await loadYmtCreators()
await loadYmtMerchants()
await loadYmtActivities()
}
if(!Array.isArray(state.exportForm.dateRange) || state.exportForm.dateRange.length!==2){ state.exportForm.dateRange = yearRange() }
state.exportVisible = true
}
@ -1035,7 +1080,7 @@ const { createApp, reactive } = Vue;
state.exportTpl = tpl
}catch(e){ msg('加载模板详情异常','error'); state.exportTpl = { id: null, filters: {}, main_table: '', fields: [], datasource: '', file_format: '' } }
}
const submitExport = async ()=>{
const submitExport = async ()=>{
const formRef = exportFormRef.value
const ok = formRef ? await formRef.validate().catch(()=>false) : true
if(!ok){ msg('请完善必填项','error'); return }
@ -1049,12 +1094,17 @@ const { createApp, reactive } = Vue;
if(state.exportForm.resellerId){ filters.reseller_id_eq = Number(state.exportForm.resellerId) }
if(state.exportForm.cashActivityId){ filters.order_cash_cash_activity_id_eq = state.exportForm.cashActivityId }
if(state.exportForm.voucherChannelActivityId){ filters.order_voucher_channel_activity_id_eq = state.exportForm.voucherChannelActivityId }
if(state.exportForm.voucherBatchChannelActivityId){ filters.voucher_batch_channel_activity_id_eq = state.exportForm.voucherBatchChannelActivityId }
if(Array.isArray(state.exportForm.creatorIds) && state.exportForm.creatorIds.length){ filters.creator_in = state.exportForm.creatorIds.map(Number) }
else if(state.exportForm.creatorIdsRaw){ const arr = String(state.exportForm.creatorIdsRaw).split(',').map(s=>s.trim()).filter(Boolean); if(arr.length){ filters.creator_in = arr } }
// 易码通专用筛选
if(state.exportForm.datasource==='ymt'){
if(String(state.exportForm.ymtCreatorId).trim()){ filters.creator_in = [ Number(state.exportForm.ymtCreatorId) ] }
if(String(state.exportForm.ymtMerchantId).trim()){ filters.reseller_id_eq = Number(state.exportForm.ymtMerchantId) }
if(String(state.exportForm.ymtActivityId).trim()){ filters.plan_id_eq = Number(state.exportForm.ymtActivityId) }
}
const payload={template_id:Number(id),requested_by:1,permission:{},options:{},filters, file_format: state.exportForm.file_format, datasource: state.exportForm.datasource};
const r=await fetch(API_BASE + '/api/exports' + qsUser(),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});
@ -1069,6 +1119,8 @@ const { createApp, reactive } = Vue;
} else { msg('任务创建返回异常','error') }
}
Vue.watch(()=>state.exportForm.creatorIds, ()=>{ state.exportForm.resellerId=null; state.exportForm.planId=null; state.exportForm.keyBatchId=null; state.exportForm.codeBatchId=null; state.exportForm.productId=null; loadResellers() })
Vue.watch(()=>state.exportForm.ymtCreatorId, ()=>{ state.exportForm.ymtMerchantId=null; state.exportForm.ymtActivityId=null; loadYmtMerchants() })
Vue.watch(()=>state.exportForm.ymtMerchantId, ()=>{ state.exportForm.ymtActivityId=null; loadYmtActivities() })
Vue.watch(()=>state.exportForm.resellerId, ()=>{ state.exportForm.planId=null; state.exportForm.keyBatchId=null; state.exportForm.codeBatchId=null; state.exportForm.productId=null; loadPlans() })
Vue.watch(()=>state.exportForm.planId, ()=>{ state.exportForm.keyBatchId=null; state.exportForm.codeBatchId=null; state.exportForm.productId=null })
Vue.watch(()=>state.exportForm.keyBatchId, ()=>{ state.exportForm.codeBatchId=null; state.exportForm.productId=null })
@ -1301,7 +1353,7 @@ const { createApp, reactive } = Vue;
loadTemplates()
loadFieldsMeta(state.form.datasource, state.form.orderType)
return { ...Vue.toRefs(state), visibilityOptions, formatOptions, datasourceOptions, fieldOptionsDynamic, editFieldOptionsDynamic, sceneOptions, editSceneOptions, loadTemplates, createTemplate, openExport, submitExport, loadJob, loadJobs, openJobs, closeJobs, download, openSQL, openEdit, saveEdit, removeTemplate, resizeDialog, createRules, exportRules, editRules, createFormRef, exportFormRef, editFormRef, dsLabel, exportType, isOrder, exportTitle, creatorOptions, resellerOptions, planOptions, hasCreators, hasReseller, hasPlan, hasKeyBatch, hasCodeBatch, jobPercent, fmtDT, fieldsCascader, editFieldsCascader, createCascaderRoot, editCascaderRoot, onCascaderVisible, onFieldsSelChange, hasUserId, currentUserId }
return { ...Vue.toRefs(state), visibilityOptions, formatOptions, datasourceOptions, fieldOptionsDynamic, editFieldOptionsDynamic, sceneOptions, editSceneOptions, loadTemplates, createTemplate, openExport, submitExport, loadJob, loadJobs, openJobs, closeJobs, download, openSQL, openEdit, saveEdit, removeTemplate, resizeDialog, createRules, exportRules, editRules, createFormRef, exportFormRef, editFormRef, dsLabel, exportType, isOrder, exportTitle, creatorOptions, ymtCreatorOptions, ymtMerchantOptions, ymtActivityOptions, resellerOptions, planOptions, hasCreators, hasReseller, hasPlan, hasKeyBatch, hasCodeBatch, jobPercent, fmtDT, fieldsCascader, editFieldsCascader, createCascaderRoot, editCascaderRoot, onCascaderVisible, onFieldsSelChange, hasUserId, currentUserId }
}
})
app.use(ElementPlus)