package testweb import ( "encoding/json" "fmt" "strings" "time" ) // ToPostmanCollection 将解析后的日志转为 Postman Collection v2.1(ApiPost 可导入)。 func ToPostmanCollection(log *ParsedAPILog, useInbound bool) ([]byte, error) { if log == nil { return nil, fmt.Errorf("nil log") } sec := log.Outbound name := "file upload outbound" if useInbound { sec = log.Inbound name = "file upload inbound" } if sec.URL == "" && log.Outbound.URL != "" { sec = log.Outbound } headers := make([]map[string]any, 0, len(sec.Headers)) for k, v := range sec.Headers { headers = append(headers, map[string]any{ "key": k, "value": v, "type": "text", }) } _, bodyObj := postmanBody(sec) url := sec.URL if url == "" { url = "{{fileTarget}}" } item := map[string]any{ "name": name, "request": map[string]any{ "method": "POST", "header": headers, "body": bodyObj, "url": url, "description": "从 forward-go api-log 导出;multipart 请在 ApiPost 中重新选择 PDF 文件", }, } if log.Response.Body != "" || log.Response.BodyJSON != "" { body := log.Response.Body if log.Response.BodyJSON != "" { body = log.Response.BodyJSON } item["response"] = []map[string]any{{ "name": "example", "status": "OK", "code": log.Response.Status, "header": responseHeaders(log.Response.Headers), "body": body, "_postman_previewlanguage": "json", }} } col := map[string]any{ "info": map[string]any{ "name": fmt.Sprintf("forward-file-%s", time.Now().Format("20060102150405")), "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", }, "item": []map[string]any{item}, } return json.MarshalIndent(col, "", " ") } func postmanBody(sec RequestSection) (mode string, body map[string]any) { ct := strings.ToLower(sec.Headers["Content-Type"]) if strings.Contains(ct, "multipart/form-data") { formdata := []map[string]any{ { "key": "file", "type": "file", "src": "", "description": "请在 ApiPost 中选择 PDF;日志中为 body_base64 落盘", }, { "key": "_note", "type": "text", "value": fmt.Sprintf("body_len=%d,从 forward api-log 导出", sec.BodyLen), "disabled": true, }, } return "formdata", map[string]any{"mode": "formdata", "formdata": formdata} } raw := sec.BodyRaw if len(sec.BodyBytes) > 0 && !sec.BodyPlain { raw = string(sec.BodyBytes) } if raw == "" && sec.BodyBase64 != "" { raw = "[base64 body, len=" + fmt.Sprint(sec.BodyLen) + "]" } return "raw", map[string]any{ "mode": "raw", "raw": raw, "options": map[string]any{ "raw": map[string]any{"language": "json"}, }, } } func responseHeaders(h map[string]string) []map[string]string { out := make([]map[string]string, 0, len(h)) for k, v := range h { out = append(out, map[string]string{"key": k, "value": v}) } return out }