diff --git a/server/internal/api/exports.go b/server/internal/api/exports.go index 303c179..6fcad7d 100644 --- a/server/internal/api/exports.go +++ b/server/internal/api/exports.go @@ -3,276 +3,348 @@ package api import ( "database/sql" "encoding/json" + "fmt" "io" "log" + "marketing-system-data-tool/server/internal/exporter" "net/http" "strconv" "strings" "time" - "marketing-system-data-tool/server/internal/exporter" ) -type ExportsAPI struct{ - meta *sql.DB - marketing *sql.DB +type ExportsAPI struct { + meta *sql.DB + marketing *sql.DB } func ExportsHandler(meta, marketing *sql.DB) http.Handler { - api := &ExportsAPI{meta: meta, marketing: marketing} - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - p := strings.TrimPrefix(r.URL.Path, "/api/exports") - if r.Method == http.MethodPost && p == "" { - api.create(w, r) - return - } - if strings.HasPrefix(p, "/") { - id := strings.TrimPrefix(p, "/") - if r.Method == http.MethodGet && !strings.HasSuffix(p, "/download") { - api.get(w, r, id) - return - } - if r.Method == http.MethodGet && strings.HasSuffix(p, "/download") { - id = strings.TrimSuffix(id, "/download") - api.download(w, r, id) - return - } - if r.Method == http.MethodPost && strings.HasSuffix(p, "/cancel") { - id = strings.TrimSuffix(id, "/cancel") - api.cancel(w, r, id) - return - } - } - w.WriteHeader(http.StatusNotFound) - }) + api := &ExportsAPI{meta: meta, marketing: marketing} + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + p := strings.TrimPrefix(r.URL.Path, "/api/exports") + if r.Method == http.MethodPost && p == "" { + api.create(w, r) + return + } + if r.Method == http.MethodGet && p == "" { + api.list(w, r) + return + } + if strings.HasPrefix(p, "/") { + id := strings.TrimPrefix(p, "/") + if r.Method == http.MethodGet && !strings.HasSuffix(p, "/download") { + api.get(w, r, id) + return + } + if r.Method == http.MethodGet && strings.HasSuffix(p, "/download") { + id = strings.TrimSuffix(id, "/download") + api.download(w, r, id) + return + } + if r.Method == http.MethodPost && strings.HasSuffix(p, "/cancel") { + id = strings.TrimSuffix(id, "/cancel") + api.cancel(w, r, id) + return + } + } + w.WriteHeader(http.StatusNotFound) + }) } -type ExportPayload struct{ - TemplateID uint64 `json:"template_id"` - RequestedBy uint64 `json:"requested_by"` - Permission map[string]interface{} `json:"permission"` - Options map[string]interface{} `json:"options"` - FileFormat string `json:"file_format"` - Filters map[string]interface{} `json:"filters"` - Datasource string `json:"datasource"` +type ExportPayload struct { + TemplateID uint64 `json:"template_id"` + RequestedBy uint64 `json:"requested_by"` + Permission map[string]interface{} `json:"permission"` + Options map[string]interface{} `json:"options"` + FileFormat string `json:"file_format"` + Filters map[string]interface{} `json:"filters"` + Datasource string `json:"datasource"` } func (a *ExportsAPI) create(w http.ResponseWriter, r *http.Request) { - b, _ := io.ReadAll(r.Body) - var p ExportPayload - json.Unmarshal(b, &p) - r = WithPayload(r, p) - var main string - var ds string - var fields []byte - log.Printf("trace_id=%s sql=%s args=%v", TraceIDFrom(r), "SELECT datasource, main_table, fields_json FROM export_templates WHERE id= ?", []interface{}{p.TemplateID}) - row := a.meta.QueryRow("SELECT datasource, main_table, fields_json FROM export_templates WHERE id= ?", p.TemplateID) - err := row.Scan(&ds, &main, &fields) - if err != nil { - fail(w, r, http.StatusBadRequest, "invalid template") - return - } - if p.Datasource != "" { ds = p.Datasource } - var fs []string - json.Unmarshal(fields, &fs) - wl := whitelist() - req := exporter.BuildRequest{MainTable: main, Fields: fs, Filters: p.Filters} - q, args, err := exporter.BuildSQL(req, wl) - if err != nil { - r = WithSQL(r, q) - fail(w, r, http.StatusBadRequest, err.Error()) - return - } - r = WithSQL(r, q) - dataDB := a.selectDataDB(ds) + b, _ := io.ReadAll(r.Body) + var p ExportPayload + json.Unmarshal(b, &p) + r = WithPayload(r, p) + var main string + var ds string + var fields []byte + log.Printf("trace_id=%s sql=%s args=%v", TraceIDFrom(r), "SELECT datasource, main_table, fields_json FROM export_templates WHERE id= ?", []interface{}{p.TemplateID}) + row := a.meta.QueryRow("SELECT datasource, main_table, fields_json FROM export_templates WHERE id= ?", p.TemplateID) + err := row.Scan(&ds, &main, &fields) + if err != nil { + fail(w, r, http.StatusBadRequest, "invalid template") + return + } + if p.Datasource != "" { + ds = p.Datasource + } + var fs []string + json.Unmarshal(fields, &fs) + wl := whitelist() + req := exporter.BuildRequest{MainTable: main, Fields: fs, Filters: p.Filters} + q, args, err := exporter.BuildSQL(req, wl) + if err != nil { + r = WithSQL(r, q) + fail(w, r, http.StatusBadRequest, err.Error()) + return + } + r = WithSQL(r, q) + dataDB := a.selectDataDB(ds) expRows, score, err := exporter.RunExplain(dataDB, q, args) if err != nil { fail(w, r, http.StatusBadRequest, err.Error()) return } - ejSQL := "INSERT INTO export_jobs (template_id, status, requested_by, permission_scope_json, filters_json, options_json, explain_json, explain_score, file_format, created_at) VALUES (?,?,?,?,?,?,?,?,?,?)" - ejArgs := []interface{}{p.TemplateID, "queued", p.RequestedBy, toJSON(p.Permission), toJSON(p.Filters), toJSON(p.Options), toJSON(expRows), score, p.FileFormat, time.Now()} - log.Printf("trace_id=%s sql=%s args=%v", TraceIDFrom(r), ejSQL, ejArgs) - res, err := a.meta.Exec(ejSQL, ejArgs...) + const passThreshold = 60 + if score < passThreshold { + fail(w, r, http.StatusBadRequest, fmt.Sprintf("EXPLAIN 未通过:评分=%d,请优化索引或缩小查询范围", score)) + return + } + var estimate int64 + for _, r := range expRows { + if r.Table.Valid && r.Table.String == "order" && r.Rows.Valid { estimate = r.Rows.Int64; break } + if r.Rows.Valid { estimate += r.Rows.Int64 } + } + ejSQL := "INSERT INTO export_jobs (template_id, status, requested_by, 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, toJSON(p.Permission), toJSON(p.Filters), toJSON(p.Options), toJSON(expRows), 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 { + fail(w, r, http.StatusInternalServerError, err.Error()) + return + } + id, _ := res.LastInsertId() + go a.runJob(uint64(id), dataDB, q, args, fs, p.FileFormat) + ok(w, r, map[string]interface{}{"id": id}) +} + +func (a *ExportsAPI) runJob(id uint64, db *sql.DB, q string, args []interface{}, cols []string, fmt string) { + log.Printf("job_id=%d sql=%s args=%v", id, "UPDATE export_jobs SET status=?, started_at=? WHERE id= ?", []interface{}{"running", time.Now(), id}) + a.meta.Exec("UPDATE export_jobs SET status=?, started_at=?, updated_at=? WHERE id= ?", "running", time.Now(), time.Now(), id) + if fmt == "csv" { + w, err := exporter.NewCSVWriter("storage", "export") + if err != nil { + a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id= ?", "failed", time.Now(), id) + return + } + w.WriteHeader(cols) + log.Printf("job_id=%d sql=%s args=%v", id, q, args) + rows, err := db.Query(q, args...) + if err != nil { + a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id= ?", "failed", time.Now(), id) + return + } + defer rows.Close() + out := make([]interface{}, len(cols)) + dest := make([]interface{}, len(cols)) + for i := range out { + dest[i] = &out[i] + } + var count int64 + var tick int64 + for rows.Next() { + if err := rows.Scan(dest...); err != nil { + a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id=?", "failed", time.Now(), id) + return + } + vals := make([]string, len(cols)) + for i := range out { + if b, ok := out[i].([]byte); ok { + vals[i] = string(b) + } else if out[i] == nil { + vals[i] = "" + } else { + vals[i] = toString(out[i]) + } + } + w.WriteRow(vals) + count++ + tick++ + if tick%500 == 0 { a.meta.Exec("UPDATE export_jobs SET total_rows=?, updated_at=? WHERE id= ?", count, time.Now(), id) } + } + path, size, _ := w.Close() + log.Printf("job_id=%d sql=%s args=%v", id, "INSERT INTO export_job_files (job_id, storage_uri, row_count, size_bytes, created_at, updated_at) VALUES (?,?,?,?,?,?)", []interface{}{id, path, count, size, time.Now(), time.Now()}) + a.meta.Exec("INSERT INTO export_job_files (job_id, storage_uri, row_count, size_bytes, created_at, updated_at) VALUES (?,?,?,?,?,?)", id, path, count, size, time.Now(), time.Now()) + log.Printf("job_id=%d sql=%s args=%v", id, "UPDATE export_jobs SET status=?, finished_at=?, total_rows=?, updated_at=? WHERE id= ?", []interface{}{"completed", time.Now(), count, time.Now(), id}) + a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=?, total_rows=?, updated_at=? WHERE id= ?", "completed", time.Now(), count, time.Now(), id) + return + } + if fmt == "xlsx" { + x, path, err := exporter.NewXLSXWriter("storage", "export", "Sheet1") + if err != nil { + a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id= ?", "failed", time.Now(), id) + return + } + x.WriteHeader(cols) + log.Printf("job_id=%d sql=%s args=%v", id, q, args) + rows, err := db.Query(q, args...) + if err != nil { + a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id= ?", "failed", time.Now(), id) + return + } + defer rows.Close() + out := make([]interface{}, len(cols)) + dest := make([]interface{}, len(cols)) + for i := range out { + dest[i] = &out[i] + } + var count int64 + for rows.Next() { + if err := rows.Scan(dest...); err != nil { + a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id=?", "failed", time.Now(), id) + return + } + vals := make([]string, len(cols)) + for i := range out { + if b, ok := out[i].([]byte); ok { + vals[i] = string(b) + } else if out[i] == nil { + vals[i] = "" + } else { + vals[i] = toString(out[i]) + } + } + x.WriteRow(vals) + count++ + } + p, size, _ := x.Close(path) + log.Printf("job_id=%d sql=%s args=%v", id, "INSERT INTO export_job_files (job_id, storage_uri, row_count, size_bytes, created_at, updated_at) VALUES (?,?,?,?,?,?)", []interface{}{id, p, count, size, time.Now(), time.Now()}) + a.meta.Exec("INSERT INTO export_job_files (job_id, storage_uri, row_count, size_bytes, created_at, updated_at) VALUES (?,?,?,?,?,?)", id, p, count, size, time.Now(), time.Now()) + log.Printf("job_id=%d sql=%s args=%v", id, "UPDATE export_jobs SET status=?, finished_at=?, total_rows=?, updated_at=? WHERE id= ?", []interface{}{"completed", time.Now(), count, time.Now(), id}) + a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=?, total_rows=?, updated_at=? WHERE id= ?", "completed", time.Now(), count, time.Now(), id) + return + } + a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=?, updated_at=? WHERE id= ?", "failed", time.Now(), time.Now(), id) +} + +func (a *ExportsAPI) selectDataDB(ds string) *sql.DB { + if ds == "ymt" { + return a.meta + } + return a.marketing +} + +func (a *ExportsAPI) get(w http.ResponseWriter, r *http.Request, id string) { + row := a.meta.QueryRow("SELECT id, template_id, status, requested_by, total_rows, file_format, started_at, finished_at, created_at, updated_at FROM export_jobs WHERE id=?", id) + var m = map[string]interface{}{} + var jid uint64 + var templateID uint64 + var status string + var requestedBy uint64 + var totalRows sql.NullInt64 + var fileFormat string + var startedAt, finishedAt sql.NullTime + var createdAt, updatedAt time.Time + err := row.Scan(&jid, &templateID, &status, &requestedBy, &totalRows, &fileFormat, &startedAt, &finishedAt, &createdAt, &updatedAt) + if err != nil { + fail(w, r, http.StatusNotFound, "not found") + return + } + m["id"] = jid + m["template_id"] = templateID + m["status"] = status + m["requested_by"] = requestedBy + m["file_format"] = fileFormat + m["total_rows"] = totalRows.Int64 + m["started_at"] = startedAt.Time + m["finished_at"] = finishedAt.Time + m["created_at"] = createdAt + m["updated_at"] = updatedAt + rows, _ := a.meta.Query("SELECT storage_uri, sheet_name, row_count, size_bytes FROM export_job_files WHERE job_id=?", id) + files := []map[string]interface{}{} + for rows.Next() { + var uri, sheet sql.NullString + var rc, sz sql.NullInt64 + rows.Scan(&uri, &sheet, &rc, &sz) + files = append(files, map[string]interface{}{"storage_uri": uri.String, "sheet_name": sheet.String, "row_count": rc.Int64, "size_bytes": sz.Int64}) + } + rows.Close() + m["files"] = files + ok(w, r, m) +} + +func (a *ExportsAPI) download(w http.ResponseWriter, r *http.Request, id string) { + row := a.meta.QueryRow("SELECT storage_uri FROM export_job_files WHERE job_id=? ORDER BY id DESC LIMIT 1", id) + var uri string + err := row.Scan(&uri) + if err != nil { + fail(w, r, http.StatusNotFound, "not found") + return + } + http.ServeFile(w, r, uri) +} + +func (a *ExportsAPI) cancel(w http.ResponseWriter, r *http.Request, id string) { + a.meta.Exec("UPDATE export_jobs SET status=?, updated_at=? WHERE id=? AND status IN ('queued','running')", "canceled", time.Now(), id) + w.Write([]byte("ok")) +} + +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 "" + } +} +func (a *ExportsAPI) list(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + page := 1 + size := 15 + if p := q.Get("page"); p != "" { + if n, err := strconv.Atoi(p); err == nil && n > 0 { page = n } + } + if s := q.Get("page_size"); s != "" { + if n, err := strconv.Atoi(s); err == nil && n > 0 && n <= 100 { size = n } + } + tplIDStr := q.Get("template_id") + var tplID uint64 + if tplIDStr != "" { + if n, err := strconv.ParseUint(tplIDStr, 10, 64); err == nil { tplID = n } + } + offset := (page - 1) * size + var totalCount int64 + if tplID > 0 { + row := a.meta.QueryRow("SELECT COUNT(1) FROM export_jobs WHERE template_id = ?", tplID) + _ = row.Scan(&totalCount) + } else { + row := a.meta.QueryRow("SELECT COUNT(1) FROM export_jobs") + _ = row.Scan(&totalCount) + } + var rows *sql.Rows + var err error + if tplID > 0 { + rows, err = a.meta.Query("SELECT id, template_id, status, requested_by, row_estimate, total_rows, file_format, created_at, updated_at, explain_score FROM export_jobs WHERE template_id = ? ORDER BY id DESC LIMIT ? OFFSET ?", tplID, size, offset) + } else { + rows, err = a.meta.Query("SELECT id, template_id, status, requested_by, row_estimate, total_rows, file_format, created_at, updated_at, explain_score FROM export_jobs ORDER BY id DESC LIMIT ? OFFSET ?", size, offset) + } if err != nil { fail(w, r, http.StatusInternalServerError, err.Error()) return } - id, _ := res.LastInsertId() - go a.runJob(uint64(id), dataDB, q, args, fs, p.FileFormat) - ok(w, r, map[string]interface{}{"id": id}) -} - -func (a *ExportsAPI) runJob(id uint64, db *sql.DB, q string, args []interface{}, cols []string, fmt string) { - log.Printf("job_id=%d sql=%s args=%v", id, "UPDATE export_jobs SET status=?, started_at=? WHERE id= ?", []interface{}{"running", time.Now(), id}) - a.meta.Exec("UPDATE export_jobs SET status=?, started_at=? WHERE id= ?", "running", time.Now(), id) - if fmt == "csv" { - w, err := exporter.NewCSVWriter("storage", "export") - if err != nil { - a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id= ?", "failed", time.Now(), id) - return - } - w.WriteHeader(cols) - log.Printf("job_id=%d sql=%s args=%v", id, q, args) - rows, err := db.Query(q, args...) - if err != nil { - a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id= ?", "failed", time.Now(), id) - return - } - defer rows.Close() - out := make([]interface{}, len(cols)) - dest := make([]interface{}, len(cols)) - for i := range out { - dest[i] = &out[i] - } - var count int64 - for rows.Next() { - if err := rows.Scan(dest...); err != nil { - a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id=?", "failed", time.Now(), id) - return - } - vals := make([]string, len(cols)) - for i := range out { - if b, ok := out[i].([]byte); ok { - vals[i] = string(b) - } else if out[i] == nil { - vals[i] = "" - } else { - vals[i] = toString(out[i]) - } - } - w.WriteRow(vals) - count++ - } - path, size, _ := w.Close() - log.Printf("job_id=%d sql=%s args=%v", id, "INSERT INTO export_job_files (job_id, storage_uri, row_count, size_bytes, created_at) VALUES (?,?,?,?,?)", []interface{}{id, path, count, size, time.Now()}) - a.meta.Exec("INSERT INTO export_job_files (job_id, storage_uri, row_count, size_bytes, created_at) VALUES (?,?,?,?,?)", id, path, count, size, time.Now()) - log.Printf("job_id=%d sql=%s args=%v", id, "UPDATE export_jobs SET status=?, finished_at=?, total_rows=? WHERE id= ?", []interface{}{"completed", time.Now(), count, id}) - a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=?, total_rows=? WHERE id=?", "completed", time.Now(), count, id) - return - } - if fmt == "xlsx" { - x, path, err := exporter.NewXLSXWriter("storage", "export", "Sheet1") - if err != nil { - a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id= ?", "failed", time.Now(), id) - return - } - x.WriteHeader(cols) - log.Printf("job_id=%d sql=%s args=%v", id, q, args) - rows, err := db.Query(q, args...) - if err != nil { - a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id= ?", "failed", time.Now(), id) - return - } - defer rows.Close() - out := make([]interface{}, len(cols)) - dest := make([]interface{}, len(cols)) - for i := range out { - dest[i] = &out[i] - } - var count int64 - for rows.Next() { - if err := rows.Scan(dest...); err != nil { - a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id=?", "failed", time.Now(), id) - return - } - vals := make([]string, len(cols)) - for i := range out { - if b, ok := out[i].([]byte); ok { - vals[i] = string(b) - } else if out[i] == nil { - vals[i] = "" - } else { - vals[i] = toString(out[i]) - } - } - x.WriteRow(vals) - count++ - } - p, size, _ := x.Close(path) - log.Printf("job_id=%d sql=%s args=%v", id, "INSERT INTO export_job_files (job_id, storage_uri, row_count, size_bytes, created_at) VALUES (?,?,?,?,?)", []interface{}{id, p, count, size, time.Now()}) - a.meta.Exec("INSERT INTO export_job_files (job_id, storage_uri, row_count, size_bytes, created_at) VALUES (?,?,?,?,?)", id, p, count, size, time.Now()) - log.Printf("job_id=%d sql=%s args=%v", id, "UPDATE export_jobs SET status=?, finished_at=?, total_rows=? WHERE id= ?", []interface{}{"completed", time.Now(), count, id}) - a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=?, total_rows=? WHERE id=?", "completed", time.Now(), count, id) - return - } - a.meta.Exec("UPDATE export_jobs SET status=?, finished_at=? WHERE id= ?", "failed", time.Now(), id) -} - -func (a *ExportsAPI) selectDataDB(ds string) *sql.DB { - if ds == "ymt" { - return a.meta - } - return a.marketing -} - -func (a *ExportsAPI) get(w http.ResponseWriter, r *http.Request, id string) { - row := a.meta.QueryRow("SELECT id, template_id, status, requested_by, total_rows, file_format, started_at, finished_at, created_at, updated_at FROM export_jobs WHERE id=?", id) - var m = map[string]interface{}{} - var jid uint64 - var templateID uint64 - var status string - var requestedBy uint64 - var totalRows sql.NullInt64 - var fileFormat string - var startedAt, finishedAt sql.NullTime - var createdAt, updatedAt time.Time - err := row.Scan(&jid, &templateID, &status, &requestedBy, &totalRows, &fileFormat, &startedAt, &finishedAt, &createdAt, &updatedAt) - if err != nil { - fail(w, r, http.StatusNotFound, "not found") - return - } - m["id"] = jid - m["template_id"] = templateID - m["status"] = status - m["requested_by"] = requestedBy - m["file_format"] = fileFormat - m["total_rows"] = totalRows.Int64 - m["started_at"] = startedAt.Time - m["finished_at"] = finishedAt.Time - m["created_at"] = createdAt - m["updated_at"] = updatedAt - rows, _ := a.meta.Query("SELECT storage_uri, sheet_name, row_count, size_bytes FROM export_job_files WHERE job_id=?", id) - files := []map[string]interface{}{} + defer rows.Close() + items := []map[string]interface{}{} for rows.Next() { - var uri, sheet sql.NullString - var rc, sz sql.NullInt64 - rows.Scan(&uri, &sheet, &rc, &sz) - files = append(files, map[string]interface{}{"storage_uri": uri.String, "sheet_name": sheet.String, "row_count": rc.Int64, "size_bytes": sz.Int64}) - } - rows.Close() - m["files"] = files - ok(w, r, m) -} - -func (a *ExportsAPI) download(w http.ResponseWriter, r *http.Request, id string) { - row := a.meta.QueryRow("SELECT storage_uri FROM export_job_files WHERE job_id=? ORDER BY id DESC LIMIT 1", id) - var uri string - err := row.Scan(&uri) - if err != nil { - fail(w, r, http.StatusNotFound, "not found") - return - } - http.ServeFile(w, r, uri) -} - -func (a *ExportsAPI) cancel(w http.ResponseWriter, r *http.Request, id string) { - a.meta.Exec("UPDATE export_jobs SET status=? WHERE id=? AND status IN ('queued','running')", "canceled", id) - w.Write([]byte("ok")) -} - -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 "" + var id, tid, req uint64 + var status, fmtstr string + var estimate, total sql.NullInt64 + var createdAt, updatedAt sql.NullTime + var score sql.NullInt64 + if err := rows.Scan(&id, &tid, &status, &req, &estimate, &total, &fmtstr, &createdAt, &updatedAt, &score); err != nil { continue } + evalStatus := "通过" + if score.Int64 < 60 { evalStatus = "禁止" } + desc := fmt.Sprintf("评分:%d,估算行数:%d;%s", score.Int64, estimate.Int64, map[bool]string{true:"允许执行", false:"禁止执行"}[score.Int64>=60]) + m := map[string]interface{}{"id": id, "template_id": tid, "status": status, "requested_by": req, "row_estimate": estimate.Int64, "total_rows": total.Int64, "file_format": fmtstr, "created_at": createdAt.Time, "updated_at": updatedAt.Time, "eval_status": evalStatus, "eval_desc": desc} + items = append(items, m) } + ok(w, r, map[string]interface{}{"items": items, "total": totalCount, "page": page, "page_size": size}) } diff --git a/server/internal/api/resellers.go b/server/internal/api/resellers.go new file mode 100644 index 0000000..a268835 --- /dev/null +++ b/server/internal/api/resellers.go @@ -0,0 +1,72 @@ +package api + +import ( + "database/sql" + "net/http" + "strconv" + "strings" +) + +type ResellersAPI struct { + marketing *sql.DB +} + +func ResellersHandler(marketing *sql.DB) http.Handler { + api := &ResellersAPI{marketing: marketing} + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + p := strings.TrimPrefix(r.URL.Path, "/api/resellers") + if r.Method == http.MethodGet && p == "" { + api.list(w, r) + return + } + w.WriteHeader(http.StatusNotFound) + }) +} + +func (a *ResellersAPI) list(w http.ResponseWriter, r *http.Request) { + creatorsParam := r.URL.Query().Get("creator") + 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 } + } + creators := []string{} + for _, s := range strings.Split(creatorsParam, ",") { + s = strings.TrimSpace(s) + if s != "" { creators = append(creators, s) } + } + if len(creators) == 0 { + ok(w, r, []map[string]interface{}{}) + return + } + ph := strings.Repeat("?,", len(creators)) + ph = strings.TrimSuffix(ph, ",") + sql1 := "SELECT DISTINCT reseller_id, COALESCE(reseller_name,'') AS name FROM plan WHERE reseller_id IS NOT NULL AND creator IN (" + ph + ")" + args := []interface{}{} + for _, c := range creators { args = append(args, c) } + if q != "" { + sql1 += " AND (CAST(reseller_id AS CHAR) LIKE ? OR reseller_name LIKE ?)" + like := "%" + q + "%" + args = append(args, like, like) + } + sql1 += " ORDER BY reseller_id ASC LIMIT ?" + args = append(args, limit) + rows, err := a.marketing.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 } + m := map[string]interface{}{"id": id.Int64, "name": name.String} + out = append(out, m) + } + ok(w, r, out) +} + diff --git a/server/internal/api/router.go b/server/internal/api/router.go index 65e12c8..0c37f62 100644 --- a/server/internal/api/router.go +++ b/server/internal/api/router.go @@ -14,6 +14,8 @@ func NewRouter(metaDB *sql.DB, marketingDB *sql.DB) http.Handler { mux.Handle("/api/exports/", withAccess(withTrace(ExportsHandler(metaDB, marketingDB)))) mux.Handle("/api/creators", withAccess(withTrace(CreatorsHandler(marketingDB)))) mux.Handle("/api/creators/", withAccess(withTrace(CreatorsHandler(marketingDB)))) + mux.Handle("/api/resellers", withAccess(withTrace(ResellersHandler(marketingDB)))) + mux.Handle("/api/resellers/", withAccess(withTrace(ResellersHandler(marketingDB)))) sd := staticDir() mux.Handle("/", http.FileServer(http.Dir(sd))) return mux diff --git a/server/log/server-20251125.log b/server/log/server-20251125.log index a2a21e1..93ff1bf 100644 --- a/server/log/server-20251125.log +++ b/server/log/server-20251125.log @@ -147,3 +147,154 @@ YMT DSN: root:lansexiongdi6,@tcp(47.97.27.195:3306)/merketing?parseTime=True&loc Marketing DSN: root:lansexiongdi@tcp(192.168.6.92:3306)/market?parseTime=True&loc=Local&charset=utf8mb4 server listening on :8077 {"bytes":1021,"duration_ms":49,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:33:47+08:00"} +{"bytes":1021,"duration_ms":135,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:34:00+08:00"} +{"bytes":1430,"duration_ms":111,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:34:03+08:00"} +{"bytes":1614,"duration_ms":19,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:34:03+08:00"} +{"bytes":1021,"duration_ms":49,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:34:08+08:00"} +{"bytes":1430,"duration_ms":101,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:34:12+08:00"} +{"bytes":1614,"duration_ms":11,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:34:12+08:00"} +{"bytes":1021,"duration_ms":48,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:35:18+08:00"} +{"bytes":1430,"duration_ms":100,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:35:20+08:00"} +{"bytes":1614,"duration_ms":11,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:35:20+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 +YMT DSN: root:lansexiongdi6,@tcp(47.97.27.195:3306)/merketing?parseTime=True&loc=Local&charset=utf8mb4 +Marketing DSN: root:lansexiongdi@tcp(192.168.6.92:3306)/market?parseTime=True&loc=Local&charset=utf8mb4 +server listening on :8077 +{"bytes":1021,"duration_ms":49,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:38:20+08:00"} +{"bytes":1430,"duration_ms":107,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:39:08+08:00"} +{"bytes":1614,"duration_ms":18,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:39:08+08:00"} +{"bytes":1021,"duration_ms":53,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:39:12+08:00"} +{"bytes":1430,"duration_ms":95,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:39:14+08:00"} +{"bytes":1614,"duration_ms":15,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:39:14+08:00"} +{"bytes":213,"duration_ms":9,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:39:16+08:00"} +{"bytes":3605,"duration_ms":12,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:39:20+08:00"} +{"bytes":213,"duration_ms":21,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:39:25+08:00"} +{"bytes":112,"duration_ms":11,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:39:29+08:00"} +{"bytes":1430,"duration_ms":95,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:39:40+08:00"} +{"bytes":1614,"duration_ms":23,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:39:40+08:00"} +{"bytes":1021,"duration_ms":49,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:39:55+08:00"} +{"bytes":1430,"duration_ms":135,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:39:58+08:00"} +{"bytes":1614,"duration_ms":44,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:39:58+08:00"} +{"bytes":3605,"duration_ms":21,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:40:09+08:00"} +{"bytes":3605,"duration_ms":12,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:40:13+08:00"} +trace_id=139b1ae6a7c5a2d20e8b6e04ce20a17b sql=SELECT datasource, main_table, fields_json FROM export_templates WHERE id= ? args=[14] +sql=EXPLAIN SELECT `order`.order_number,`order`.creator,`order`.out_trade_no,`order`.type,`order`.status,`order`.contract_price,`order`.num,`order`.total,`order`.pay_amount,`order`.create_time,`order`.update_time,`order_detail`.plan_title,`order_detail`.reseller_name,`order_detail`.product_name,`order_detail`.show_url,`order_detail`.official_price,`order_detail`.cost_price,`order_detail`.create_time,`order_detail`.update_time,`order_cash`.channel,`order_cash`.cash_activity_id,`order_cash`.receive_status,`order_cash`.receive_time,`order_cash`.cash_packet_id,`order_cash`.cash_id,`order_cash`.amount,`order_cash`.status,`order_cash`.expire_time,`order_cash`.update_time,`plan`.id,`plan`.title,`plan`.status,`plan`.begin_time,`plan`.end_time,`key_batch`.id,`key_batch`.batch_name,`key_batch`.bind_object,`key_batch`.quantity,`key_batch`.stock,`key_batch`.begin_time,`key_batch`.end_time,`code_batch`.id,`code_batch`.title,`code_batch`.status,`code_batch`.begin_time,`code_batch`.end_time,`code_batch`.quantity,`code_batch`.usage,`code_batch`.stock FROM `order` LEFT JOIN `order_detail` ON `order_detail`.order_number = `order`.order_number LEFT JOIN `order_cash` ON `order_cash`.order_number = `order`.order_number LEFT JOIN `plan` ON `plan`.id = `order`.plan_id LEFT JOIN `key_batch` ON `key_batch`.plan_id = `plan`.id LEFT JOIN `code_batch` ON `code_batch`.key_batch_id = `key_batch`.id WHERE `order`.creator IN (?) AND `order`.create_time BETWEEN ? AND ? args=[1 2025-11-01 00:00:00 2025-12-31 00:00:00] +trace_id=139b1ae6a7c5a2d20e8b6e04ce20a17b sql=INSERT INTO export_jobs (template_id, status, requested_by, permission_scope_json, filters_json, options_json, explain_json, explain_score, file_format, created_at) VALUES (?,?,?,?,?,?,?,?,?,?) args=[14 queued 1 [123 125] [123 34 99 114 101 97 116 101 95 116 105 109 101 95 98 101 116 119 101 101 110 34 58 91 34 50 48 50 53 45 49 49 45 48 49 32 48 48 58 48 48 58 48 48 34 44 34 50 48 50 53 45 49 50 45 51 49 32 48 48 58 48 48 58 48 48 34 93 44 34 99 114 101 97 116 111 114 95 105 110 34 58 91 49 93 125] [123 125] [91 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 111 114 100 101 114 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 114 97 110 103 101 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 105 100 120 95 99 114 101 97 116 101 95 116 105 109 101 95 115 116 97 116 117 115 95 99 114 101 97 116 111 114 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 105 100 120 95 99 114 101 97 116 101 95 116 105 109 101 95 115 116 97 116 117 115 95 99 114 101 97 116 111 114 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 54 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 111 114 100 101 114 95 100 101 116 97 105 108 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 101 113 95 114 101 102 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 56 50 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 111 114 100 101 114 95 99 97 115 104 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 101 113 95 114 101 102 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 44 111 95 117 105 100 120 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 56 50 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 112 108 97 110 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 101 113 95 114 101 102 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 52 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 107 101 121 95 98 97 116 99 104 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 65 76 76 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 99 111 100 101 95 98 97 116 99 104 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 114 101 102 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 107 101 121 95 98 97 116 99 104 95 105 100 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 107 101 121 95 98 97 116 99 104 95 105 100 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 52 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 93] 100 xlsx 2025-11-25 14:40:18.254068 +0800 CST m=+129.217624792] +trace_id=139b1ae6a7c5a2d20e8b6e04ce20a17b status=500 file=response.go:43 method=POST path=/api/exports query= remote=[::1]:59663 sql=SELECT `order`.order_number,`order`.creator,`order`.out_trade_no,`order`.type,`order`.status,`order`.contract_price,`order`.num,`order`.total,`order`.pay_amount,`order`.create_time,`order`.update_time,`order_detail`.plan_title,`order_detail`.reseller_name,`order_detail`.product_name,`order_detail`.show_url,`order_detail`.official_price,`order_detail`.cost_price,`order_detail`.create_time,`order_detail`.update_time,`order_cash`.channel,`order_cash`.cash_activity_id,`order_cash`.receive_status,`order_cash`.receive_time,`order_cash`.cash_packet_id,`order_cash`.cash_id,`order_cash`.amount,`order_cash`.status,`order_cash`.expire_time,`order_cash`.update_time,`plan`.id,`plan`.title,`plan`.status,`plan`.begin_time,`plan`.end_time,`key_batch`.id,`key_batch`.batch_name,`key_batch`.bind_object,`key_batch`.quantity,`key_batch`.stock,`key_batch`.begin_time,`key_batch`.end_time,`code_batch`.id,`code_batch`.title,`code_batch`.status,`code_batch`.begin_time,`code_batch`.end_time,`code_batch`.quantity,`code_batch`.usage,`code_batch`.stock FROM `order` LEFT JOIN `order_detail` ON `order_detail`.order_number = `order`.order_number LEFT JOIN `order_cash` ON `order_cash`.order_number = `order`.order_number LEFT JOIN `plan` ON `plan`.id = `order`.plan_id LEFT JOIN `key_batch` ON `key_batch`.plan_id = `plan`.id LEFT JOIN `code_batch` ON `code_batch`.key_batch_id = `key_batch`.id WHERE `order`.creator IN (?) AND `order`.create_time BETWEEN ? AND ? payload={"template_id":14,"requested_by":1,"permission":{},"options":{},"file_format":"xlsx","filters":{"create_time_between":["2025-11-01 00:00:00","2025-12-31 00:00:00"],"creator_in":[1]},"datasource":"marketing"} msg=Error 1364 (HY000): Field 'updated_at' doesn't have a default value +{"bytes":144,"duration_ms":227,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":500,"trace_id":"","ts":"2025-11-25T14:40:18+08:00"} +{"bytes":1430,"duration_ms":112,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:40:30+08:00"} +{"bytes":1614,"duration_ms":12,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:40:30+08:00"} +{"bytes":1430,"duration_ms":100,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:40:35+08:00"} +{"bytes":1614,"duration_ms":20,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:40:36+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 +YMT DSN: root:lansexiongdi6,@tcp(47.97.27.195:3306)/merketing?parseTime=True&loc=Local&charset=utf8mb4 +Marketing DSN: root:lansexiongdi@tcp(192.168.6.92:3306)/market?parseTime=True&loc=Local&charset=utf8mb4 +server listening on :8077 +{"bytes":1021,"duration_ms":54,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:41:56+08:00"} +{"bytes":1430,"duration_ms":93,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:53:09+08:00"} +{"bytes":1614,"duration_ms":24,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:53:09+08:00"} +{"bytes":1021,"duration_ms":50,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:53:16+08:00"} +{"bytes":1430,"duration_ms":91,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:53:17+08:00"} +{"bytes":1614,"duration_ms":8,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:53:17+08:00"} +{"bytes":1021,"duration_ms":44,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:54:09+08:00"} +{"bytes":1430,"duration_ms":94,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:54:10+08:00"} +{"bytes":1614,"duration_ms":20,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:54:10+08:00"} +{"bytes":1021,"duration_ms":48,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:55:04+08:00"} +{"bytes":1430,"duration_ms":91,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:55:05+08:00"} +{"bytes":1614,"duration_ms":11,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:55:05+08:00"} +{"bytes":1021,"duration_ms":47,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:55:31+08:00"} +{"bytes":1430,"duration_ms":94,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:55:32+08:00"} +{"bytes":1614,"duration_ms":12,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:55:32+08:00"} +{"bytes":3605,"duration_ms":13,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:55:41+08:00"} +{"bytes":3605,"duration_ms":12,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:55:48+08:00"} +{"bytes":3605,"duration_ms":12,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:55:52+08:00"} +{"bytes":3605,"duration_ms":19,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:55:55+08:00"} +{"bytes":3605,"duration_ms":37,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T14:55:55+08:00"} +trace_id=4dfd3ab8db7af3a3d3c1e9347bf6f8dd sql=SELECT datasource, main_table, fields_json FROM export_templates WHERE id= ? args=[14] +sql=EXPLAIN SELECT `order`.order_number,`order`.creator,`order`.out_trade_no,`order`.type,`order`.status,`order`.contract_price,`order`.num,`order`.total,`order`.pay_amount,`order`.create_time,`order`.update_time,`order_detail`.plan_title,`order_detail`.reseller_name,`order_detail`.product_name,`order_detail`.show_url,`order_detail`.official_price,`order_detail`.cost_price,`order_detail`.create_time,`order_detail`.update_time,`order_cash`.channel,`order_cash`.cash_activity_id,`order_cash`.receive_status,`order_cash`.receive_time,`order_cash`.cash_packet_id,`order_cash`.cash_id,`order_cash`.amount,`order_cash`.status,`order_cash`.expire_time,`order_cash`.update_time,`plan`.id,`plan`.title,`plan`.status,`plan`.begin_time,`plan`.end_time,`key_batch`.id,`key_batch`.batch_name,`key_batch`.bind_object,`key_batch`.quantity,`key_batch`.stock,`key_batch`.begin_time,`key_batch`.end_time,`code_batch`.id,`code_batch`.title,`code_batch`.status,`code_batch`.begin_time,`code_batch`.end_time,`code_batch`.quantity,`code_batch`.usage,`code_batch`.stock FROM `order` LEFT JOIN `order_detail` ON `order_detail`.order_number = `order`.order_number LEFT JOIN `order_cash` ON `order_cash`.order_number = `order`.order_number LEFT JOIN `plan` ON `plan`.id = `order`.plan_id LEFT JOIN `key_batch` ON `key_batch`.plan_id = `plan`.id LEFT JOIN `code_batch` ON `code_batch`.key_batch_id = `key_batch`.id WHERE `order`.creator IN (?) AND `order`.create_time BETWEEN ? AND ? args=[1 2025-01-01 00:00:00 2025-12-31 23:59:59] +trace_id=4dfd3ab8db7af3a3d3c1e9347bf6f8dd sql=INSERT INTO export_jobs (template_id, status, requested_by, permission_scope_json, filters_json, options_json, explain_json, explain_score, file_format, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?) args=[14 queued 1 [123 125] [123 34 99 114 101 97 116 101 95 116 105 109 101 95 98 101 116 119 101 101 110 34 58 91 34 50 48 50 53 45 48 49 45 48 49 32 48 48 58 48 48 58 48 48 34 44 34 50 48 50 53 45 49 50 45 51 49 32 50 51 58 53 57 58 53 57 34 93 44 34 99 114 101 97 116 111 114 95 105 110 34 58 91 49 93 125] [123 125] [91 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 111 114 100 101 114 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 114 97 110 103 101 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 105 100 120 95 99 114 101 97 116 101 95 116 105 109 101 95 115 116 97 116 117 115 95 99 114 101 97 116 111 114 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 105 100 120 95 99 114 101 97 116 101 95 116 105 109 101 95 115 116 97 116 117 115 95 99 114 101 97 116 111 114 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 54 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 111 114 100 101 114 95 100 101 116 97 105 108 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 101 113 95 114 101 102 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 56 50 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 111 114 100 101 114 95 99 97 115 104 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 101 113 95 114 101 102 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 44 111 95 117 105 100 120 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 56 50 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 112 108 97 110 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 101 113 95 114 101 102 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 52 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 107 101 121 95 98 97 116 99 104 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 65 76 76 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 99 111 100 101 95 98 97 116 99 104 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 114 101 102 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 107 101 121 95 98 97 116 99 104 95 105 100 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 107 101 121 95 98 97 116 99 104 95 105 100 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 52 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 93] 100 xlsx 2025-11-25 15:01:58.788369 +0800 CST m=+1071.165826668 2025-11-25 15:01:58.788369 +0800 CST m=+1071.165826710] +{"bytes":83,"duration_ms":205,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:01:58+08:00"} +job_id=1 sql=UPDATE export_jobs SET status=?, started_at=? WHERE id= ? args=[running 2025-11-25 15:01:58.884743 +0800 CST m=+1071.262201501 1] +job_id=1 sql=SELECT `order`.order_number,`order`.creator,`order`.out_trade_no,`order`.type,`order`.status,`order`.contract_price,`order`.num,`order`.total,`order`.pay_amount,`order`.create_time,`order`.update_time,`order_detail`.plan_title,`order_detail`.reseller_name,`order_detail`.product_name,`order_detail`.show_url,`order_detail`.official_price,`order_detail`.cost_price,`order_detail`.create_time,`order_detail`.update_time,`order_cash`.channel,`order_cash`.cash_activity_id,`order_cash`.receive_status,`order_cash`.receive_time,`order_cash`.cash_packet_id,`order_cash`.cash_id,`order_cash`.amount,`order_cash`.status,`order_cash`.expire_time,`order_cash`.update_time,`plan`.id,`plan`.title,`plan`.status,`plan`.begin_time,`plan`.end_time,`key_batch`.id,`key_batch`.batch_name,`key_batch`.bind_object,`key_batch`.quantity,`key_batch`.stock,`key_batch`.begin_time,`key_batch`.end_time,`code_batch`.id,`code_batch`.title,`code_batch`.status,`code_batch`.begin_time,`code_batch`.end_time,`code_batch`.quantity,`code_batch`.usage,`code_batch`.stock FROM `order` LEFT JOIN `order_detail` ON `order_detail`.order_number = `order`.order_number LEFT JOIN `order_cash` ON `order_cash`.order_number = `order`.order_number LEFT JOIN `plan` ON `plan`.id = `order`.plan_id LEFT JOIN `key_batch` ON `key_batch`.plan_id = `plan`.id LEFT JOIN `code_batch` ON `code_batch`.key_batch_id = `key_batch`.id WHERE `order`.creator IN (?) AND `order`.create_time BETWEEN ? AND ? args=[1 2025-01-01 00:00:00 2025-12-31 23:59:59] +job_id=1 sql=INSERT INTO export_job_files (job_id, storage_uri, row_count, size_bytes, created_at, updated_at) VALUES (?,?,?,?,?,?) args=[1 storage/export_20251125150158.xlsx 0 6536 2025-11-25 15:01:58.999267 +0800 CST m=+1071.376726001 2025-11-25 15:01:58.999267 +0800 CST m=+1071.376726126] +job_id=1 sql=UPDATE export_jobs SET status=?, finished_at=?, total_rows=?, updated_at=? WHERE id= ? args=[completed 2025-11-25 15:01:59.043325 +0800 CST m=+1071.420784085 0 2025-11-25 15:01:59.043325 +0800 CST m=+1071.420784335 1] +{"bytes":350,"duration_ms":515,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:01:59+08:00"} +{"bytes":1021,"duration_ms":47,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:02:14+08:00"} +{"bytes":1430,"duration_ms":92,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:02:19+08:00"} +{"bytes":1614,"duration_ms":10,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:02:20+08:00"} +{"bytes":1369,"duration_ms":108,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:02:23+08:00"} +{"bytes":1614,"duration_ms":10,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:02:24+08:00"} +{"bytes":3605,"duration_ms":13,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:02:28+08:00"} +trace_id=5fcc5f0745003259333eadb64e4fc015 sql=SELECT datasource, main_table, fields_json FROM export_templates WHERE id= ? args=[12] +sql=EXPLAIN SELECT `order`.order_number,`order`.creator,`order`.out_trade_no,`order`.type,`order`.status,`order`.contract_price,`order`.num,`order`.total,`order`.pay_amount,`order`.create_time,`order`.update_time,`order_detail`.plan_title,`order_detail`.reseller_name,`order_detail`.product_name,`order_detail`.show_url,`order_detail`.official_price,`order_detail`.cost_price,`order_detail`.create_time,`order_detail`.update_time,`plan`.id,`plan`.title,`plan`.status,`plan`.begin_time,`plan`.end_time,`key_batch`.id,`key_batch`.batch_name,`key_batch`.bind_object,`key_batch`.quantity,`key_batch`.stock,`key_batch`.begin_time,`key_batch`.end_time,`code_batch`.id,`code_batch`.title,`code_batch`.status,`code_batch`.begin_time,`code_batch`.end_time,`code_batch`.quantity,`code_batch`.usage,`code_batch`.stock,`merchant_key_send`.merchant_id,`merchant_key_send`.out_biz_no,`merchant_key_send`.`key`,`merchant_key_send`.status,`merchant_key_send`.usage_time,`merchant_key_send`.create_time FROM `order` LEFT JOIN `order_detail` ON `order_detail`.order_number = `order`.order_number LEFT JOIN `plan` ON `plan`.id = `order`.plan_id LEFT JOIN `key_batch` ON `key_batch`.plan_id = `plan`.id LEFT JOIN `code_batch` ON `code_batch`.key_batch_id = `key_batch`.id LEFT JOIN `merchant_key_send` ON `order`.`key` = `merchant_key_send`.key WHERE `order`.creator IN (?) AND `order`.create_time BETWEEN ? AND ? args=[1 2025-01-01 00:00:00 2025-12-31 23:59:59] +trace_id=5fcc5f0745003259333eadb64e4fc015 sql=INSERT INTO export_jobs (template_id, status, requested_by, permission_scope_json, filters_json, options_json, explain_json, explain_score, file_format, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?) args=[12 queued 1 [123 125] [123 34 99 114 101 97 116 101 95 116 105 109 101 95 98 101 116 119 101 101 110 34 58 91 34 50 48 50 53 45 48 49 45 48 49 32 48 48 58 48 48 58 48 48 34 44 34 50 48 50 53 45 49 50 45 51 49 32 50 51 58 53 57 58 53 57 34 93 44 34 99 114 101 97 116 111 114 95 105 110 34 58 91 49 93 125] [123 125] [91 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 111 114 100 101 114 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 114 97 110 103 101 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 105 100 120 95 99 114 101 97 116 101 95 116 105 109 101 95 115 116 97 116 117 115 95 99 114 101 97 116 111 114 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 105 100 120 95 99 114 101 97 116 101 95 116 105 109 101 95 115 116 97 116 117 115 95 99 114 101 97 116 111 114 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 54 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 111 114 100 101 114 95 100 101 116 97 105 108 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 101 113 95 114 101 102 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 56 50 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 112 108 97 110 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 101 113 95 114 101 102 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 80 82 73 77 65 82 89 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 52 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 107 101 121 95 98 97 116 99 104 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 65 76 76 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 99 111 100 101 95 98 97 116 99 104 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 114 101 102 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 107 101 121 95 98 97 116 99 104 95 105 100 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 107 101 121 95 98 97 116 99 104 95 105 100 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 52 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 44 123 34 73 68 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 83 101 108 101 99 116 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 83 73 77 80 76 69 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 97 98 108 101 34 58 123 34 83 116 114 105 110 103 34 58 34 109 101 114 99 104 97 110 116 95 107 101 121 95 115 101 110 100 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 84 121 112 101 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 80 111 115 115 105 98 108 101 75 101 121 115 34 58 123 34 83 116 114 105 110 103 34 58 34 114 101 102 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 34 58 123 34 83 116 114 105 110 103 34 58 34 117 100 120 95 107 101 121 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 75 101 121 76 101 110 34 58 123 34 83 116 114 105 110 103 34 58 34 117 100 120 95 107 101 121 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 101 102 34 58 123 34 83 116 114 105 110 103 34 58 34 49 50 50 34 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 82 111 119 115 34 58 123 34 73 110 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 116 114 117 101 125 44 34 70 105 108 116 101 114 101 100 34 58 123 34 70 108 111 97 116 54 52 34 58 48 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 44 34 69 120 116 114 97 34 58 123 34 83 116 114 105 110 103 34 58 34 34 44 34 86 97 108 105 100 34 58 102 97 108 115 101 125 125 93] 100 xlsx 2025-11-25 15:02:31.75134 +0800 CST m=+1104.128954710 2025-11-25 15:02:31.75134 +0800 CST m=+1104.128954835] +{"bytes":83,"duration_ms":205,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:02:31+08:00"} +job_id=2 sql=UPDATE export_jobs SET status=?, started_at=? WHERE id= ? args=[running 2025-11-25 15:02:31.851175 +0800 CST m=+1104.228790918 2] +job_id=2 sql=SELECT `order`.order_number,`order`.creator,`order`.out_trade_no,`order`.type,`order`.status,`order`.contract_price,`order`.num,`order`.total,`order`.pay_amount,`order`.create_time,`order`.update_time,`order_detail`.plan_title,`order_detail`.reseller_name,`order_detail`.product_name,`order_detail`.show_url,`order_detail`.official_price,`order_detail`.cost_price,`order_detail`.create_time,`order_detail`.update_time,`plan`.id,`plan`.title,`plan`.status,`plan`.begin_time,`plan`.end_time,`key_batch`.id,`key_batch`.batch_name,`key_batch`.bind_object,`key_batch`.quantity,`key_batch`.stock,`key_batch`.begin_time,`key_batch`.end_time,`code_batch`.id,`code_batch`.title,`code_batch`.status,`code_batch`.begin_time,`code_batch`.end_time,`code_batch`.quantity,`code_batch`.usage,`code_batch`.stock,`merchant_key_send`.merchant_id,`merchant_key_send`.out_biz_no,`merchant_key_send`.`key`,`merchant_key_send`.status,`merchant_key_send`.usage_time,`merchant_key_send`.create_time FROM `order` LEFT JOIN `order_detail` ON `order_detail`.order_number = `order`.order_number LEFT JOIN `plan` ON `plan`.id = `order`.plan_id LEFT JOIN `key_batch` ON `key_batch`.plan_id = `plan`.id LEFT JOIN `code_batch` ON `code_batch`.key_batch_id = `key_batch`.id LEFT JOIN `merchant_key_send` ON `order`.`key` = `merchant_key_send`.key WHERE `order`.creator IN (?) AND `order`.create_time BETWEEN ? AND ? args=[1 2025-01-01 00:00:00 2025-12-31 23:59:59] +job_id=2 sql=INSERT INTO export_job_files (job_id, storage_uri, row_count, size_bytes, created_at, updated_at) VALUES (?,?,?,?,?,?) args=[2 storage/export_20251125150231.xlsx 0 6501 2025-11-25 15:02:31.97477 +0800 CST m=+1104.352386043 2025-11-25 15:02:31.97477 +0800 CST m=+1104.352386126] +job_id=2 sql=UPDATE export_jobs SET status=?, finished_at=?, total_rows=?, updated_at=? WHERE id= ? args=[completed 2025-11-25 15:02:32.020952 +0800 CST m=+1104.398568418 0 2025-11-25 15:02:32.020952 +0800 CST m=+1104.398568585 2] +{"bytes":337,"duration_ms":178,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:02:32+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 +YMT DSN: root:lansexiongdi6,@tcp(47.97.27.195:3306)/merketing?parseTime=True&loc=Local&charset=utf8mb4 +Marketing DSN: root:lansexiongdi@tcp(192.168.6.92:3306)/market?parseTime=True&loc=Local&charset=utf8mb4 +server listening on :8077 +{"bytes":440,"duration_ms":48,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:04:13+08:00"} +{"bytes":1021,"duration_ms":372,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:04:14+08:00"} +{"bytes":440,"duration_ms":58,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:04:21+08:00"} +{"bytes":1021,"duration_ms":59,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:04:21+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 +YMT DSN: root:lansexiongdi6,@tcp(47.97.27.195:3306)/merketing?parseTime=True&loc=Local&charset=utf8mb4 +Marketing DSN: root:lansexiongdi@tcp(192.168.6.92:3306)/market?parseTime=True&loc=Local&charset=utf8mb4 +server listening on :8077 +{"bytes":1021,"duration_ms":47,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:06:18+08:00"} +{"bytes":484,"duration_ms":494,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:06:18+08:00"} +{"bytes":1021,"duration_ms":49,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:06:37+08:00"} +{"bytes":484,"duration_ms":147,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:06:38+08:00"} +trace_id=e670a4285657d7202f967015ba28ecdb status=404 file=response.go:43 method=GET path=/api/exports/2/download query=ide_webview_request_time=1764054403690 remote=[::1]:57917 payload= msg=not found +{"bytes":86,"duration_ms":96,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":404,"trace_id":"","ts":"2025-11-25T15:06:43+08:00"} +{"bytes":1021,"duration_ms":50,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:06:51+08:00"} +{"bytes":484,"duration_ms":148,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:06:51+08:00"} +{"bytes":1021,"duration_ms":54,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:08:02+08:00"} +{"bytes":484,"duration_ms":150,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:08:02+08:00"} +{"bytes":1021,"duration_ms":53,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:11:16+08:00"} +{"bytes":484,"duration_ms":155,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:11:16+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 +YMT DSN: root:lansexiongdi6,@tcp(47.97.27.195:3306)/merketing?parseTime=True&loc=Local&charset=utf8mb4 +Marketing DSN: root:lansexiongdi@tcp(192.168.6.92:3306)/market?parseTime=True&loc=Local&charset=utf8mb4 +server listening on :8077 +{"bytes":1021,"duration_ms":48,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:12:21+08:00"} +{"bytes":518,"duration_ms":500,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:12:21+08:00"} +{"bytes":1021,"duration_ms":59,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:12:55+08:00"} +{"bytes":518,"duration_ms":156,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:12:55+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 +YMT DSN: root:lansexiongdi6,@tcp(47.97.27.195:3306)/merketing?parseTime=True&loc=Local&charset=utf8mb4 +Marketing DSN: root:lansexiongdi@tcp(192.168.6.92:3306)/market?parseTime=True&loc=Local&charset=utf8mb4 +server listening on :8077 +{"bytes":1021,"duration_ms":59,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:14:39+08:00"} +{"bytes":1021,"duration_ms":47,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:15:41+08:00"} +{"bytes":319,"duration_ms":202,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:15:43+08:00"} +{"bytes":1803,"duration_ms":101,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:15:49+08:00"} +{"bytes":1614,"duration_ms":12,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:15:49+08:00"} +{"bytes":319,"duration_ms":202,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:15:52+08:00"} +{"bytes":319,"duration_ms":295,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:15:52+08:00"} +{"bytes":319,"duration_ms":232,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:15:55+08:00"} +{"bytes":350,"duration_ms":203,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:15:57+08:00"} +{"bytes":1021,"duration_ms":71,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:17:53+08:00"} +{"bytes":1430,"duration_ms":131,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:17:54+08:00"} +{"bytes":1614,"duration_ms":33,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:17:54+08:00"} +{"bytes":121,"duration_ms":243,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:17:57+08:00"} +{"bytes":319,"duration_ms":196,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:18:02+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 +YMT DSN: root:lansexiongdi6,@tcp(47.97.27.195:3306)/merketing?parseTime=True&loc=Local&charset=utf8mb4 +Marketing DSN: root:lansexiongdi@tcp(192.168.6.92:3306)/market?parseTime=True&loc=Local&charset=utf8mb4 +server listening on :8077 +{"bytes":1021,"duration_ms":46,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:21:33+08:00"} +{"bytes":1021,"duration_ms":57,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:22:52+08:00"} +{"bytes":399,"duration_ms":204,"kind":"access","level":"INFO","method":"","path":"","query":"","remote":"","status":200,"trace_id":"","ts":"2025-11-25T15:22:53+08:00"} diff --git a/server/storage/export_20251125150158.xlsx b/server/storage/export_20251125150158.xlsx new file mode 100755 index 0000000..17aea93 Binary files /dev/null and b/server/storage/export_20251125150158.xlsx differ diff --git a/server/storage/export_20251125150231.xlsx b/server/storage/export_20251125150231.xlsx new file mode 100755 index 0000000..c48b41a Binary files /dev/null and b/server/storage/export_20251125150231.xlsx differ diff --git a/web/index.html b/web/index.html index 7be8568..80bfe52 100644 --- a/web/index.html +++ b/web/index.html @@ -38,21 +38,38 @@ - -
- 任务 {{ job.id }} 状态:{{ job.status }} 行数:{{ job.total_rows || '' }} - 下载 + + + + + + + + + + + + + + + +
+
关闭
+
-
暂无任务
+
暂无任务
@@ -143,13 +160,13 @@ 保存 - + 筛选条件 - + - + @@ -164,7 +181,9 @@ - + + + diff --git a/web/main.js b/web/main.js index 4b6eb9d..da33c38 100644 --- a/web/main.js +++ b/web/main.js @@ -3,6 +3,12 @@ const { createApp, reactive } = Vue; setup(){ const state = reactive({ templates: [], + jobs: [], + jobsVisible: false, + jobsTplId: null, + jobsPage: 1, + jobsPageSize: 15, + jobsTotal: 0, job: {}, form: { name: '', @@ -345,6 +351,7 @@ const { createApp, reactive } = Vue; return v || '' } const creatorOptions = Vue.ref([]) + const resellerOptions = 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) @@ -358,6 +365,16 @@ const { createApp, reactive } = Vue; creatorOptions.value = arr.map(it=>({label: it.name || String(it.id), value: Number(it.id)})) }catch(_e){ creatorOptions.value = [] } } + const loadResellers = async ()=>{ + const ids = Array.isArray(state.exportForm.creatorIds) ? state.exportForm.creatorIds : [] + if(!ids.length){ resellerOptions.value = []; return } + try{ + const res = await fetch(API_BASE + '/api/resellers?creator=' + ids.join(',')) + const data = await res.json() + const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : []) + resellerOptions.value = arr.map(it=>({label: (it.name||'') + (it.name?'':'') , value: Number(it.id)})) + }catch(_e){ resellerOptions.value = [] } + } const exportType = Vue.computed(()=>{ const f = state.exportTpl && state.exportTpl.filters if(!f) return null @@ -404,6 +421,12 @@ const { createApp, reactive } = Vue; const pad=(n)=>String(n).padStart(2,'0'); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; } + const yearRange = ()=>{ + const now = new Date() + const start = new Date(now.getFullYear(), 0, 1, 0, 0, 0) + const end = new Date(now.getFullYear(), 11, 31, 23, 59, 59) + return [ fmtDT(start), fmtDT(end) ] + } const loadTemplates = async ()=>{ try{ const res = await fetch(API_BASE + '/api/templates'); @@ -420,6 +443,33 @@ const { createApp, reactive } = Vue; state.templates = [] } } + const loadJobs = async (page)=>{ + if(!page) page = state.jobsPage + try{ + const qs = new URLSearchParams() + qs.set('page', String(page)) + qs.set('page_size', String(state.jobsPageSize)) + if(state.jobsTplId){ qs.set('template_id', String(state.jobsTplId)) } + const res = await fetch(API_BASE + '/api/exports?' + qs.toString()); + if(!res.ok){ state.jobs = []; return } + const data = await res.json(); + const payload = data?.data || data || {} + const arr = Array.isArray(payload.items) ? payload.items : (Array.isArray(payload) ? payload : []) + state.jobs = arr + state.jobsTotal = Number(payload.total || 0) + state.jobsPage = Number(payload.page || page) + }catch(_e){ state.jobs = [] } + } + const openJobs = (row)=>{ state.jobsTplId = row.id; state.jobsVisible = true; loadJobs(1) } + const closeJobs = ()=>{ state.jobsVisible = false } + const jobPercent = (row)=>{ + const est = Number(row.row_estimate || 0) + const done = Number(row.total_rows || 0) + if(row.status==='completed') return '100%' + if(row.status==='queued') return '0%' + if(est>0 && done>=0){ const p = Math.max(0, Math.min(100, Math.floor(done*100/est))); return p + '%' } + return row.status || '' + } const createTemplate = async ()=>{ const formRef = createFormRef.value const ok = formRef ? await formRef.validate().catch(()=>false) : true @@ -463,6 +513,7 @@ const { createApp, reactive } = Vue; 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() } + if(!Array.isArray(state.exportForm.dateRange) || state.exportForm.dateRange.length!==2){ state.exportForm.dateRange = yearRange() } state.exportVisible = true } const loadTemplateDetail = async (id)=>{ @@ -501,9 +552,9 @@ const { createApp, reactive } = Vue; const j=await r.json(); const jid = j?.data?.id ?? j?.id state.exportVisible=false - if(jid){ loadJob(jid) } else { msg('任务创建返回异常','error') } + if(jid){ loadJob(jid); loadJobs() } 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 }) + 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.resellerId, ()=>{ state.exportForm.planId=null; state.exportForm.keyBatchId=null; state.exportForm.codeBatchId=null; state.exportForm.productId=null }) 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 }) @@ -562,7 +613,8 @@ const { createApp, reactive } = Vue; } const download = (id)=>{ window.open(API_BASE + '/api/exports/'+id+'/download','_blank') } loadTemplates() - return { ...Vue.toRefs(state), visibilityOptions, formatOptions, datasourceOptions, fieldOptions, loadTemplates, createTemplate, openExport, submitExport, loadJob, download, openEdit, saveEdit, removeTemplate, resizeDialog, createRules, exportRules, editRules, createFormRef, exportFormRef, editFormRef, dsLabel, exportType, isOrder, exportTitle } + + return { ...Vue.toRefs(state), visibilityOptions, formatOptions, datasourceOptions, fieldOptions, loadTemplates, createTemplate, openExport, submitExport, loadJob, loadJobs, openJobs, closeJobs, download, openEdit, saveEdit, removeTemplate, resizeDialog, createRules, exportRules, editRules, createFormRef, exportFormRef, editFormRef, dsLabel, exportType, isOrder, exportTitle, creatorOptions, resellerOptions, hasCreators, hasReseller, hasPlan, hasKeyBatch, hasCodeBatch, jobPercent } } }) app.use(ElementPlus)