优化抢购
This commit is contained in:
+68
-30
@@ -216,7 +216,7 @@ func (c *DouyuClient) Score(cookie string) (int, error) {
|
||||
Balance int `json:"balance"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := c.doJSON(req, &result, "积分余额"); err != nil {
|
||||
if _, err := c.doJSON(req, &result, "积分余额"); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if result.Error != 0 {
|
||||
@@ -245,44 +245,60 @@ func (c *DouyuClient) GenerateCSRF(cookie string) (string, error) {
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
c.trace.record("生成CSRF", req, resp.StatusCode, time.Since(start), reqBody, nil, err.Error(), 0, "")
|
||||
c.trace.recordResponse("生成CSRF", req, resp, resp.StatusCode, time.Since(start), reqBody, nil, err.Error(), 0, "")
|
||||
logNetworkError("生成CSRF", req, time.Since(start), err)
|
||||
return "", err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.trace.record("生成CSRF", req, resp.StatusCode, time.Since(start), reqBody, body, "", 0, "")
|
||||
c.trace.recordResponse("生成CSRF", req, resp, resp.StatusCode, time.Since(start), reqBody, body, "", 0, "")
|
||||
logNetworkHTTPError("生成CSRF", req, resp.StatusCode, time.Since(start), body)
|
||||
return "", fmt.Errorf("CSRF HTTP %d: %s", resp.StatusCode, trimForLog(string(body), 120))
|
||||
}
|
||||
|
||||
var api APIResponse
|
||||
if err := json.Unmarshal(body, &api); err != nil {
|
||||
c.trace.record("生成CSRF", req, resp.StatusCode, time.Since(start), reqBody, body, err.Error(), 0, "")
|
||||
c.trace.recordResponse("生成CSRF", req, resp, resp.StatusCode, time.Since(start), reqBody, body, err.Error(), 0, "")
|
||||
logNetworkParseError("生成CSRF", req, time.Since(start), body, err)
|
||||
return "", fmt.Errorf("CSRF 响应解析失败: %w", err)
|
||||
}
|
||||
traceErr := ""
|
||||
if api.Error != 0 {
|
||||
traceErr = api.Msg
|
||||
c.trace.record("生成CSRF", req, resp.StatusCode, time.Since(start), reqBody, body, traceErr, api.Error, api.Msg)
|
||||
c.trace.recordResponse("生成CSRF", req, resp, resp.StatusCode, time.Since(start), reqBody, body, traceErr, api.Error, api.Msg)
|
||||
logNetworkAPIResult("生成CSRF", req, time.Since(start), api.Error, api.Msg)
|
||||
return "", fmt.Errorf("CSRF 接口失败: %s", api.Msg)
|
||||
}
|
||||
for _, ck := range resp.Cookies() {
|
||||
if ck.Name == "cvl_csrf_token" && ck.Value != "" {
|
||||
c.trace.record("生成CSRF", req, resp.StatusCode, time.Since(start), reqBody, body, "", api.Error, api.Msg)
|
||||
c.trace.recordResponse("生成CSRF", req, resp, resp.StatusCode, time.Since(start), reqBody, body, "", api.Error, api.Msg)
|
||||
logNetworkSuccess("生成CSRF", req, time.Since(start), 0, "")
|
||||
return ck.Value, nil
|
||||
}
|
||||
}
|
||||
c.trace.record("生成CSRF", req, resp.StatusCode, time.Since(start), reqBody, body, "响应没有 cvl_csrf_token", api.Error, api.Msg)
|
||||
c.trace.recordResponse("生成CSRF", req, resp, resp.StatusCode, time.Since(start), reqBody, body, "响应没有 cvl_csrf_token", api.Error, api.Msg)
|
||||
logEvent("网络", "warn", "接口返回异常: 生成CSRF %s %s 耗时=%s msg=响应没有 cvl_csrf_token",
|
||||
req.Method, requestPath(req), roundDuration(time.Since(start)))
|
||||
return "", errors.New("CSRF 响应没有 cvl_csrf_token")
|
||||
}
|
||||
|
||||
func csrfFromResponse(resp *http.Response) string {
|
||||
if resp == nil {
|
||||
return ""
|
||||
}
|
||||
for _, ck := range resp.Cookies() {
|
||||
if ck.Name == "cvl_csrf_token" && ck.Value != "" {
|
||||
return ck.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// BatchExchangeLimit 请求兑换限制接口,返回本次允许的批量上限。
|
||||
func (c *DouyuClient) BatchExchangeLimit(cookie, commodityID, csrf string) (int, error) {
|
||||
func (c *DouyuClient) BatchExchangeLimit(cookie, commodityID string, csrf *string) (int, error) {
|
||||
currentCSRF := ""
|
||||
if csrf != nil {
|
||||
currentCSRF = *csrf
|
||||
}
|
||||
var result struct {
|
||||
APIResponse
|
||||
Data struct {
|
||||
@@ -293,7 +309,7 @@ func (c *DouyuClient) BatchExchangeLimit(cookie, commodityID, csrf string) (int,
|
||||
"manualID": manualID,
|
||||
"commodityID": commodityID,
|
||||
"rid": roomID,
|
||||
"csrfToken": csrf,
|
||||
"csrfToken": currentCSRF,
|
||||
}, &result); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -308,14 +324,18 @@ func (c *DouyuClient) BatchExchangeLimit(cookie, commodityID, csrf string) (int,
|
||||
}
|
||||
|
||||
// PreExchange 按抓包流程执行预兑换检查。
|
||||
func (c *DouyuClient) PreExchange(cookie, commodityID, csrf string) error {
|
||||
func (c *DouyuClient) PreExchange(cookie, commodityID string, csrf *string) error {
|
||||
currentCSRF := ""
|
||||
if csrf != nil {
|
||||
currentCSRF = *csrf
|
||||
}
|
||||
var result APIResponse
|
||||
if err := c.postMultipart(preExchangeURL, cookie, csrf, "预兑换", map[string]string{
|
||||
"manualID": manualID,
|
||||
"rid": roomID,
|
||||
"commodityID": commodityID,
|
||||
"num": "1",
|
||||
"csrfToken": csrf,
|
||||
"csrfToken": currentCSRF,
|
||||
}, &result); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -326,7 +346,11 @@ func (c *DouyuClient) PreExchange(cookie, commodityID, csrf string) error {
|
||||
}
|
||||
|
||||
// Exchange 正式兑换一个商品,num 固定为 1,和浏览器抓包一致。
|
||||
func (c *DouyuClient) Exchange(cookie, commodityID, csrf string) (string, error) {
|
||||
func (c *DouyuClient) Exchange(cookie, commodityID string, csrf *string) (string, error) {
|
||||
currentCSRF := ""
|
||||
if csrf != nil {
|
||||
currentCSRF = *csrf
|
||||
}
|
||||
var result struct {
|
||||
APIResponse
|
||||
Data struct {
|
||||
@@ -340,7 +364,7 @@ func (c *DouyuClient) Exchange(cookie, commodityID, csrf string) (string, error)
|
||||
"commodityID": commodityID,
|
||||
"num": "1",
|
||||
"rid": roomID,
|
||||
"csrfToken": csrf,
|
||||
"csrfToken": currentCSRF,
|
||||
}, &result); err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -379,11 +403,12 @@ func (c *DouyuClient) getJSON(url, cookie, label string, out any) error {
|
||||
return err
|
||||
}
|
||||
c.setBrowserHeaders(req, cookie)
|
||||
return c.doJSON(req, out, label)
|
||||
_, err = c.doJSON(req, out, label)
|
||||
return err
|
||||
}
|
||||
|
||||
// postMultipart 发送 multipart/form-data 请求并解析 JSON。
|
||||
func (c *DouyuClient) postMultipart(url, cookie, csrf, label string, fields map[string]string, out any) error {
|
||||
// postMultipart 发送 multipart/form-data 请求并解析 JSON,并同步响应里的 CSRF Cookie。
|
||||
func (c *DouyuClient) postMultipart(url, cookie string, csrf *string, label string, fields map[string]string, out any) error {
|
||||
var body bytes.Buffer
|
||||
writer := multipart.NewWriter(&body)
|
||||
for key, value := range fields {
|
||||
@@ -399,49 +424,62 @@ func (c *DouyuClient) postMultipart(url, cookie, csrf, label string, fields map[
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.setBrowserHeaders(req, mergeCookie(cookie, "cvl_csrf_token", csrf))
|
||||
currentCSRF := ""
|
||||
if csrf != nil {
|
||||
currentCSRF = *csrf
|
||||
}
|
||||
c.setBrowserHeaders(req, mergeCookie(cookie, "cvl_csrf_token", currentCSRF))
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
return c.doJSON(req, out, label)
|
||||
resp, err := c.doJSON(req, out, label)
|
||||
if resp != nil {
|
||||
if nextCSRF := csrfFromResponse(resp); nextCSRF != "" && csrf != nil {
|
||||
if nextCSRF != *csrf {
|
||||
logEvent("网络", "info", "响应刷新CSRF已同步: %s %s %s", label, req.Method, requestPath(req))
|
||||
}
|
||||
*csrf = nextCSRF
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// doJSON 执行请求、检查 HTTP 状态码并解析 JSON。
|
||||
func (c *DouyuClient) doJSON(req *http.Request, out any, label string) error {
|
||||
func (c *DouyuClient) doJSON(req *http.Request, out any, label string) (*http.Response, error) {
|
||||
start := time.Now()
|
||||
reqBody := traceRequestBody(c.trace, req)
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
c.trace.record(label, req, 0, time.Since(start), reqBody, nil, err.Error(), 0, "")
|
||||
logNetworkError(label, req, time.Since(start), err)
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
c.trace.record(label, req, resp.StatusCode, time.Since(start), reqBody, nil, err.Error(), 0, "")
|
||||
c.trace.recordResponse(label, req, resp, resp.StatusCode, time.Since(start), reqBody, nil, err.Error(), 0, "")
|
||||
logNetworkError(label, req, time.Since(start), err)
|
||||
return err
|
||||
return resp, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.trace.record(label, req, resp.StatusCode, time.Since(start), reqBody, body, "", 0, "")
|
||||
c.trace.recordResponse(label, req, resp, resp.StatusCode, time.Since(start), reqBody, body, "", 0, "")
|
||||
logNetworkHTTPError(label, req, resp.StatusCode, time.Since(start), body)
|
||||
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, trimForLog(string(body), 160))
|
||||
return resp, fmt.Errorf("HTTP %d: %s", resp.StatusCode, trimForLog(string(body), 160))
|
||||
}
|
||||
if err := json.Unmarshal(body, out); err != nil {
|
||||
c.trace.record(label, req, resp.StatusCode, time.Since(start), reqBody, body, err.Error(), 0, "")
|
||||
c.trace.recordResponse(label, req, resp, resp.StatusCode, time.Since(start), reqBody, body, err.Error(), 0, "")
|
||||
logNetworkParseError(label, req, time.Since(start), body, err)
|
||||
return fmt.Errorf("JSON 解析失败: %w, body=%s", err, trimForLog(string(body), 160))
|
||||
return resp, fmt.Errorf("JSON 解析失败: %w, body=%s", err, trimForLog(string(body), 160))
|
||||
}
|
||||
if apiError, apiMsg, ok := extractAPIStatus(out); ok && apiError != 0 {
|
||||
c.trace.record(label, req, resp.StatusCode, time.Since(start), reqBody, body, apiMsg, apiError, apiMsg)
|
||||
c.trace.recordResponse(label, req, resp, resp.StatusCode, time.Since(start), reqBody, body, apiMsg, apiError, apiMsg)
|
||||
logNetworkAPIResult(label, req, time.Since(start), apiError, apiMsg)
|
||||
} else if shouldLogNetworkSuccess(label) {
|
||||
c.trace.record(label, req, resp.StatusCode, time.Since(start), reqBody, body, "", 0, "")
|
||||
c.trace.recordResponse(label, req, resp, resp.StatusCode, time.Since(start), reqBody, body, "", 0, "")
|
||||
logNetworkSuccess(label, req, time.Since(start), 0, "")
|
||||
} else {
|
||||
c.trace.record(label, req, resp.StatusCode, time.Since(start), reqBody, body, "", 0, "")
|
||||
c.trace.recordResponse(label, req, resp, resp.StatusCode, time.Since(start), reqBody, body, "", 0, "")
|
||||
}
|
||||
return nil
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func shouldLogNetworkSuccess(label string) bool {
|
||||
|
||||
@@ -91,6 +91,10 @@ func (s *appState) handleSeckill(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, errors.New("请选择账号和商品"))
|
||||
return
|
||||
}
|
||||
if !s.tryStartRun() {
|
||||
writeError(w, http.StatusConflict, errors.New("抢购任务正在运行,请勿重复提交"))
|
||||
return
|
||||
}
|
||||
go s.runNow(cloneStringMap(req.AccountProducts))
|
||||
writeJSON(w, map[string]any{"success": true, "msg": "抢购已启动"})
|
||||
}
|
||||
|
||||
+25
-4
@@ -18,6 +18,7 @@ const (
|
||||
|
||||
// runNow 立即执行一批抢购任务。
|
||||
func (s *appState) runNow(accountProducts map[string]string) {
|
||||
defer s.finishRun()
|
||||
s.clearResults()
|
||||
attempts := s.buildAttempts(accountProducts)
|
||||
if len(attempts) == 0 {
|
||||
@@ -50,6 +51,22 @@ func (s *appState) runNow(accountProducts map[string]string) {
|
||||
logEvent("抢购", "info", "抢购任务结束")
|
||||
}
|
||||
|
||||
func (s *appState) tryStartRun() bool {
|
||||
s.runMu.Lock()
|
||||
defer s.runMu.Unlock()
|
||||
if s.running {
|
||||
return false
|
||||
}
|
||||
s.running = true
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *appState) finishRun() {
|
||||
s.runMu.Lock()
|
||||
defer s.runMu.Unlock()
|
||||
s.running = false
|
||||
}
|
||||
|
||||
// buildAttempts 根据前端传入的账号到商品映射,生成具体兑换尝试。
|
||||
func (s *appState) buildAttempts(accountProducts map[string]string) []ExchangeAttempt {
|
||||
accounts := s.accountSnapshot()
|
||||
@@ -186,7 +203,7 @@ func (s *appState) stopSchedulesLocked() {
|
||||
}
|
||||
|
||||
// runAttempt 按抓包链路执行一次兑换:CSRF -> 限制 -> 预兑换 -> 正式兑换。
|
||||
// 同一次抢购尝试只生成一个 CSRF,后续接口共用,减少临门请求耗时。
|
||||
// 同一次抢购尝试只生成一次 CSRF;如果后续响应刷新 token,则沿链路继续复用最新值。
|
||||
func (s *appState) runAttempt(attempt ExchangeAttempt) ExchangeResult {
|
||||
result := ExchangeResult{
|
||||
Account: attempt.Account.Name,
|
||||
@@ -212,7 +229,7 @@ func (s *appState) runAttempt(attempt ExchangeAttempt) ExchangeResult {
|
||||
logEvent("抢购", "info", "CSRF已生成,本次兑换链路复用同一个 token: 账号=%s 商品=%s", attempt.Account.Name, attempt.Product.CommodityName)
|
||||
}
|
||||
|
||||
limit, err := s.client.BatchExchangeLimit(attempt.Account.Cookie, attempt.Product.CommodityID, csrf)
|
||||
limit, err := s.client.BatchExchangeLimit(attempt.Account.Cookie, attempt.Product.CommodityID, &csrf)
|
||||
if err != nil {
|
||||
result.Message = "失败|批量限制检查失败: " + err.Error()
|
||||
return result
|
||||
@@ -223,12 +240,12 @@ func (s *appState) runAttempt(attempt ExchangeAttempt) ExchangeResult {
|
||||
}
|
||||
logEvent("抢购", "info", "批量限制通过: 账号=%s 商品=%s limit=%d", attempt.Account.Name, attempt.Product.CommodityName, limit)
|
||||
|
||||
if err := s.client.PreExchange(attempt.Account.Cookie, attempt.Product.CommodityID, csrf); err != nil {
|
||||
if err := s.client.PreExchange(attempt.Account.Cookie, attempt.Product.CommodityID, &csrf); err != nil {
|
||||
result.Message = "失败|预兑换失败: " + err.Error()
|
||||
return result
|
||||
}
|
||||
|
||||
exchangeInfo, err := s.client.Exchange(attempt.Account.Cookie, attempt.Product.CommodityID, csrf)
|
||||
exchangeInfo, err := s.client.Exchange(attempt.Account.Cookie, attempt.Product.CommodityID, &csrf)
|
||||
if err != nil {
|
||||
result.Message = "失败|" + err.Error()
|
||||
return result
|
||||
@@ -352,6 +369,10 @@ func (s *appState) scheduleTimers(accountProducts map[string]string, attempts []
|
||||
estimatedServer.In(cst).Format("15:04:05.000"),
|
||||
calibration.Offset.Milliseconds(),
|
||||
calibration.ErrorBound.Milliseconds())
|
||||
if !s.tryStartRun() {
|
||||
logEvent("抢购", "warn", "已有抢购任务正在运行,本次定时触发已跳过")
|
||||
return
|
||||
}
|
||||
s.runNow(accountProducts)
|
||||
})
|
||||
if resync {
|
||||
|
||||
@@ -42,9 +42,9 @@ func initTrace(logDir string, cfg TraceConfig) (*traceRecorder, func()) {
|
||||
filePath: path,
|
||||
}
|
||||
if cfg.MaxBodyBytes > 0 {
|
||||
logEvent("Trace", "warn", "trace 已启用: %s,单段正文最多保留 %d 字节,不做脱敏", path, cfg.MaxBodyBytes)
|
||||
logEvent("Trace", "warn", "trace 已启用: %s,单段正文最多保留 %d 字节;请求头不脱敏,响应 Set-Cookie 隐藏值", path, cfg.MaxBodyBytes)
|
||||
} else {
|
||||
logEvent("Trace", "warn", "trace 已启用: %s,全量保留请求和响应,不做脱敏", path)
|
||||
logEvent("Trace", "warn", "trace 已启用: %s,全量保留请求和响应;请求头不脱敏,响应 Set-Cookie 隐藏值", path)
|
||||
}
|
||||
return recorder, func() {
|
||||
_ = recorder.Close()
|
||||
@@ -60,6 +60,10 @@ func (t *traceRecorder) Close() error {
|
||||
}
|
||||
|
||||
func (t *traceRecorder) record(label string, req *http.Request, status int, cost time.Duration, reqBody, respBody []byte, errText string, apiError int, apiMsg string) {
|
||||
t.recordResponse(label, req, nil, status, cost, reqBody, respBody, errText, apiError, apiMsg)
|
||||
}
|
||||
|
||||
func (t *traceRecorder) recordResponse(label string, req *http.Request, resp *http.Response, status int, cost time.Duration, reqBody, respBody []byte, errText string, apiError int, apiMsg string) {
|
||||
if t == nil || !t.enabled || req == nil || req.URL == nil {
|
||||
return
|
||||
}
|
||||
@@ -76,6 +80,7 @@ func (t *traceRecorder) record(label string, req *http.Request, status int, cost
|
||||
Status: status,
|
||||
Cost: cost,
|
||||
ReqHeader: sanitizeHeader(req.Header),
|
||||
RespHeader: sanitizeResponseHeader(resp),
|
||||
ReqBody: sanitizeBodyForTrace(reqBody, req.Header.Get("Content-Type"), t.maxBodyBytes),
|
||||
RespBody: sanitizeResponseBody(respBody, t.maxBodyBytes),
|
||||
APIError: apiError,
|
||||
@@ -114,6 +119,66 @@ func sanitizeHeader(header http.Header) map[string][]string {
|
||||
return out
|
||||
}
|
||||
|
||||
func sanitizeResponseHeader(resp *http.Response) map[string][]string {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
out := sanitizeHeader(resp.Header)
|
||||
if len(out) == 0 {
|
||||
return out
|
||||
}
|
||||
if _, ok := out["Set-Cookie"]; ok {
|
||||
out["Set-Cookie"] = summarizeSetCookies(resp)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func summarizeSetCookies(resp *http.Response) []string {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
cookies := resp.Cookies()
|
||||
if len(cookies) == 0 {
|
||||
values := resp.Header.Values("Set-Cookie")
|
||||
summary := make([]string, 0, len(values))
|
||||
for range values {
|
||||
summary = append(summary, "<hidden>")
|
||||
}
|
||||
return summary
|
||||
}
|
||||
summary := make([]string, 0, len(cookies))
|
||||
for _, ck := range cookies {
|
||||
if ck == nil || ck.Name == "" {
|
||||
continue
|
||||
}
|
||||
parts := []string{ck.Name + "=<hidden>"}
|
||||
if ck.Path != "" {
|
||||
parts = append(parts, "Path="+ck.Path)
|
||||
}
|
||||
if ck.Domain != "" {
|
||||
parts = append(parts, "Domain="+ck.Domain)
|
||||
}
|
||||
if ck.MaxAge != 0 {
|
||||
parts = append(parts, fmt.Sprintf("Max-Age=%d", ck.MaxAge))
|
||||
}
|
||||
if !ck.Expires.IsZero() {
|
||||
parts = append(parts, "Expires="+ck.Expires.In(cst).Format(time.RFC3339))
|
||||
}
|
||||
if ck.HttpOnly {
|
||||
parts = append(parts, "HttpOnly")
|
||||
}
|
||||
if ck.Secure {
|
||||
parts = append(parts, "Secure")
|
||||
}
|
||||
summary = append(summary, strings.Join(parts, "; "))
|
||||
}
|
||||
if len(summary) == 0 {
|
||||
return []string{"<hidden>"}
|
||||
}
|
||||
sort.Strings(summary)
|
||||
return summary
|
||||
}
|
||||
|
||||
func sanitizeBodyForTrace(body []byte, contentType string, max int) string {
|
||||
if len(body) == 0 {
|
||||
return ""
|
||||
@@ -192,6 +257,7 @@ type traceEntry struct {
|
||||
Status int
|
||||
Cost time.Duration
|
||||
ReqHeader map[string][]string
|
||||
RespHeader map[string][]string
|
||||
ReqBody string
|
||||
RespBody string
|
||||
APIError int
|
||||
@@ -220,6 +286,8 @@ func formatTraceEntry(e traceEntry) string {
|
||||
if e.ErrorText != "" {
|
||||
fmt.Fprintf(&b, "错误: %s\n", e.ErrorText)
|
||||
}
|
||||
b.WriteString("\n[响应头]\n")
|
||||
b.WriteString(formatTraceHeaders(e.RespHeader))
|
||||
b.WriteString("\n[请求头]\n")
|
||||
b.WriteString(formatTraceHeaders(e.ReqHeader))
|
||||
b.WriteString("\n[请求体]\n")
|
||||
@@ -227,9 +295,9 @@ func formatTraceEntry(e traceEntry) string {
|
||||
b.WriteString("\n[响应体]\n")
|
||||
b.WriteString(formatTraceBody(e.RespBody))
|
||||
if e.MaxBodyBytes > 0 {
|
||||
fmt.Fprintf(&b, "\n提示: 单段正文最多保留 %d 字节;trace 不做脱敏。\n", e.MaxBodyBytes)
|
||||
fmt.Fprintf(&b, "\n提示: 单段正文最多保留 %d 字节;请求头不脱敏,响应 Set-Cookie 隐藏值。\n", e.MaxBodyBytes)
|
||||
} else {
|
||||
b.WriteString("\n提示: trace 全量保留请求和响应,不做脱敏。\n")
|
||||
b.WriteString("\n提示: trace 全量保留请求和响应;请求头不脱敏,响应 Set-Cookie 隐藏值。\n")
|
||||
}
|
||||
b.WriteByte('\n')
|
||||
return b.String()
|
||||
|
||||
Reference in New Issue
Block a user