203 lines
6.5 KiB
Go
203 lines
6.5 KiB
Go
package logweb
|
||
|
||
import (
|
||
"database/sql"
|
||
"encoding/json"
|
||
"fmt"
|
||
|
||
"xk-hy-transit-go/internal/db"
|
||
"xk-hy-transit-go/internal/hy"
|
||
)
|
||
|
||
// RunListItem 同步流水列表项(任务 + 业务 + 最新日志)。
|
||
type RunListItem struct {
|
||
Job db.SyncJobRow `json:"job,omitempty"`
|
||
HasJob bool `json:"hasJob"`
|
||
Record db.RecordWithLog `json:"record"`
|
||
Step string `json:"step"`
|
||
StepLabel string `json:"stepLabel"`
|
||
PushStatusLabel string `json:"pushStatusLabel"`
|
||
CallbackStatusLabel string `json:"callbackStatusLabel"`
|
||
Callback CallbackView `json:"callback"`
|
||
Payload JSONBlock `json:"payload"`
|
||
Errors JSONBlock `json:"validationErrors"`
|
||
Response JSONBlock `json:"responseBody"`
|
||
PushPlain JSONBlock `json:"pushPlain"`
|
||
HasPushPlain bool `json:"hasPushPlain"`
|
||
}
|
||
|
||
// RecordDetailView 记录详情 API 响应。
|
||
type RecordDetailView struct {
|
||
Record db.RecordWithLog `json:"record"`
|
||
Step string `json:"step"`
|
||
StepLabel string `json:"stepLabel"`
|
||
PushStatusLabel string `json:"pushStatusLabel"`
|
||
CallbackStatusLabel string `json:"callbackStatusLabel"`
|
||
Logs []db.PushLogRow `json:"logs"`
|
||
Payload JSONBlock `json:"payload"`
|
||
Errors JSONBlock `json:"validationErrors"`
|
||
Callback CallbackView `json:"callback"`
|
||
LogViews []LogEntryView `json:"logViews"`
|
||
PushPlain JSONBlock `json:"pushPlain"`
|
||
}
|
||
|
||
// LogEntryView 单条推送日志 + 格式化明文/头/响应。
|
||
type LogEntryView struct {
|
||
Log db.PushLogRow `json:"log"`
|
||
PlainJSON JSONBlock `json:"plainJson"`
|
||
Headers JSONBlock `json:"headers"`
|
||
Response JSONBlock `json:"responseBody"`
|
||
CipherPreview string `json:"cipherPreview,omitempty"`
|
||
}
|
||
|
||
// BuildCallbackView 从记录与最新日志合成回调视图。
|
||
func BuildCallbackView(r db.RecordWithLog) CallbackView {
|
||
cbStatus := InferCallbackStatus(r.PushStatus)
|
||
cb := CallbackView{
|
||
BizKey: r.BizKey,
|
||
PushStatus: r.PushStatus,
|
||
PushStatusLabel: hy.LabelPushStatus(r.PushStatus),
|
||
CallbackStatus: cbStatus,
|
||
CallbackStatusLabel: hy.LabelCallbackStatus(cbStatus),
|
||
Note: "本机未持久化云端 record_id;完整 callback_status 以 xk-api 为准",
|
||
}
|
||
if r.LastError.Valid && r.LastError.String != "" {
|
||
cb.ErrorMessage = r.LastError.String
|
||
}
|
||
if r.LastLog != nil {
|
||
cb.HTTPCode = r.LastLog.HTTPCode
|
||
if r.LastLog.MsgCode.Valid {
|
||
cb.MsgCode = int(r.LastLog.MsgCode.Int64)
|
||
}
|
||
cb.Msg = r.LastLog.Msg
|
||
cb.TraceID = r.LastLog.TraceID
|
||
if r.LastLog.ResponseBody.Valid {
|
||
cb.ResponseBody = r.LastLog.ResponseBody.String
|
||
}
|
||
}
|
||
return cb
|
||
}
|
||
|
||
func nullStringJSON(ns sql.NullString) JSONBlock {
|
||
if !ns.Valid || ns.String == "" {
|
||
return JSONBlock{Raw: "", Valid: true}
|
||
}
|
||
return FormatJSON(ns.String)
|
||
}
|
||
|
||
// ListRuns 组装流水列表:优先按记录查,附带匹配的 job。
|
||
func ListRuns(store *db.Store, anchorDate, step string, limit int) ([]RunListItem, error) {
|
||
var records []db.RecordWithLog
|
||
var err error
|
||
if step != "" {
|
||
method, mErr := db.MethodForStep(step)
|
||
if mErr != nil {
|
||
return nil, mErr
|
||
}
|
||
if anchorDate == "" {
|
||
records, err = store.ListRecordsRecent(limit, "")
|
||
} else {
|
||
records, err = store.ListRecordsByAnchorMethod(anchorDate, method, limit)
|
||
}
|
||
} else {
|
||
records, err = store.ListRecordsRecent(limit, anchorDate)
|
||
}
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
jobs, _ := store.ListJobs(50, anchorDate, step)
|
||
jobIndex := make(map[string]db.SyncJobRow)
|
||
for _, j := range jobs {
|
||
key := j.AnchorDate + "|" + j.Step
|
||
jobIndex[key] = j
|
||
}
|
||
|
||
out := make([]RunListItem, 0, len(records))
|
||
for _, rec := range records {
|
||
st := db.StepForMethod(rec.Method)
|
||
cb := BuildCallbackView(rec)
|
||
item := RunListItem{
|
||
Record: rec,
|
||
Step: st,
|
||
StepLabel: hy.LabelStep(st),
|
||
PushStatusLabel: hy.LabelPushStatus(rec.PushStatus),
|
||
CallbackStatusLabel: cb.CallbackStatusLabel,
|
||
Callback: cb,
|
||
Payload: FormatJSON(rec.PayloadJSON),
|
||
Errors: nullStringJSON(rec.ValidationErrors),
|
||
}
|
||
if rec.LastLog != nil {
|
||
if rec.LastLog.ResponseBody.Valid {
|
||
item.Response = FormatJSON(rec.LastLog.ResponseBody.String)
|
||
}
|
||
item.PushPlain = nullStringJSON(rec.LastLog.RequestPlainJSON)
|
||
item.HasPushPlain = rec.LastLog.RequestPlainJSON.Valid && rec.LastLog.RequestPlainJSON.String != ""
|
||
}
|
||
key := rec.AnchorDate + "|" + st
|
||
if j, ok := jobIndex[key]; ok {
|
||
item.Job = j
|
||
item.HasJob = true
|
||
}
|
||
out = append(out, item)
|
||
}
|
||
return out, nil
|
||
}
|
||
|
||
// GetRecordDetail 记录详情。
|
||
func GetRecordDetail(store *db.Store, recordID int64) (*RecordDetailView, error) {
|
||
d, err := store.GetRecordDetail(recordID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
st := db.StepForMethod(d.Record.Method)
|
||
cb := BuildCallbackView(d.Record)
|
||
view := &RecordDetailView{
|
||
Record: d.Record,
|
||
Step: st,
|
||
StepLabel: hy.LabelStep(st),
|
||
PushStatusLabel: hy.LabelPushStatus(d.Record.PushStatus),
|
||
CallbackStatusLabel: cb.CallbackStatusLabel,
|
||
Logs: d.Logs,
|
||
Payload: FormatJSON(d.Record.PayloadJSON),
|
||
Errors: nullStringJSON(d.Record.ValidationErrors),
|
||
Callback: cb,
|
||
}
|
||
if len(d.Logs) > 0 {
|
||
view.PushPlain = nullStringJSON(d.Logs[0].RequestPlainJSON)
|
||
}
|
||
for _, l := range d.Logs {
|
||
resp := JSONBlock{Raw: "", Valid: true}
|
||
if l.ResponseBody.Valid {
|
||
resp = FormatJSON(l.ResponseBody.String)
|
||
}
|
||
lv := LogEntryView{
|
||
Log: l,
|
||
PlainJSON: nullStringJSON(l.RequestPlainJSON),
|
||
Headers: nullStringJSON(l.RequestHeadersJSON),
|
||
Response: resp,
|
||
}
|
||
if l.RequestBodyLen > 0 && l.RequestHeadersJSON.Valid {
|
||
// requestBody 在 headers JSON 的 requestBody 字段(密文)
|
||
lv.CipherPreview = cipherPreviewFromHeaders(l.RequestHeadersJSON.String, l.RequestBodyLen)
|
||
}
|
||
view.LogViews = append(view.LogViews, lv)
|
||
}
|
||
return view, nil
|
||
}
|
||
|
||
func cipherPreviewFromHeaders(headersJSON string, bodyLen int) string {
|
||
var h map[string]string
|
||
if err := json.Unmarshal([]byte(headersJSON), &h); err != nil {
|
||
return fmt.Sprintf("密文长度 %d 字节", bodyLen)
|
||
}
|
||
rb := h["requestBody"]
|
||
if rb == "" {
|
||
return fmt.Sprintf("密文长度 %d 字节", bodyLen)
|
||
}
|
||
if len(rb) > 200 {
|
||
return rb[:200] + "…"
|
||
}
|
||
return rb
|
||
}
|