v0.4.0:窗口调度/限流分类/可观测性,抢购改每账号独立循环,移除 CLI 只保留 GUI

This commit is contained in:
yml2213
2026-10-16 18:14:30 +08:00
parent 29aed15e4e
commit 8b3b14654f
20 changed files with 2309 additions and 664 deletions
+88
View File
@@ -3,9 +3,12 @@ package wup
import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io"
"sort"
"strings"
"huya-go/internal/taf"
)
@@ -261,3 +264,88 @@ func NormalizePayload(data []byte) []byte {
}
return data
}
// 诊断串里的取值上限:服务端错误信息可能很长,日志里只需要够判断原因的前缀。
const (
diagnoseValueLimit = 80
diagnoseFieldLimit = 6
)
// Diagnose 汇总一次 WUP 响应里可用于排障的字段。
//
// 「响应缺少 tRsp/tResp」这种报错过去只有一句文案,服务端的 iRet / status /
// context 全被丢掉,事后只能靠抓包复现(2026-10-16 pc-3 日志里同一账号连续 13 次
// 命中该错误,却看不出任何原因)。这里把 tag3(iRet)、tag9(context)、tag10(status)
// 与 tResp 数据体的 key 一并带出来,一次日志就能定性。
func (r *Response) Diagnose() string {
if r == nil {
return "响应为空"
}
parts := []string{fmt.Sprintf("iRet=%d", r.Message)}
if r.Servant != "" || r.Func != "" {
parts = append(parts, fmt.Sprintf("servant=%s.%s", r.Servant, r.Func))
}
if text := formatDiagnoseMap(r.Status); text != "" {
parts = append(parts, "status="+text)
}
if text := formatDiagnoseMap(r.Context); text != "" {
parts = append(parts, "context="+text)
}
if len(r.NewData) == 0 {
parts = append(parts, "keys=空")
} else {
parts = append(parts, "keys="+formatDiagnoseKeys(r.NewData))
}
parts = append(parts, fmt.Sprintf("payload=%dB", len(r.Buffer)))
return strings.Join(parts, " ")
}
// formatDiagnoseMap 按键名排序输出 map,值过长时截断,保证日志稳定且不刷屏。
func formatDiagnoseMap(values map[string]string) string {
if len(values) == 0 {
return ""
}
keys := make([]string, 0, len(values))
for key := range values {
keys = append(keys, key)
}
sort.Strings(keys)
parts := make([]string, 0, len(keys))
for index, key := range keys {
if index >= diagnoseFieldLimit {
parts = append(parts, fmt.Sprintf("…(共%d项)", len(keys)))
break
}
parts = append(parts, fmt.Sprintf("%s=%s", key, truncateDiagnoseValue(values[key])))
}
return "{" + strings.Join(parts, " ") + "}"
}
// formatDiagnoseKeys 输出 tResp 数据体的键名(值可能是任意结构,只列键)。
func formatDiagnoseKeys(values map[string][]byte) string {
keys := make([]string, 0, len(values))
for key := range values {
keys = append(keys, fmt.Sprintf("%s(%dB)", key, len(values[key])))
}
sort.Strings(keys)
return "{" + strings.Join(keys, " ") + "}"
}
func truncateDiagnoseValue(value string) string {
trimmed := strings.TrimSpace(value)
if len(trimmed) <= diagnoseValueLimit {
return trimmed
}
return trimmed[:diagnoseValueLimit] + "…"
}
// HexHead 把二进制帧的前 n 字节转成十六进制,用于 debug 级日志定位协议问题。
func HexHead(data []byte, n int) string {
if n <= 0 || len(data) == 0 {
return ""
}
if n > len(data) {
n = len(data)
}
return hex.EncodeToString(data[:n])
}
+54
View File
@@ -1,6 +1,7 @@
package wup_test
import (
"strings"
"testing"
"huya-go/internal/huya"
@@ -31,3 +32,56 @@ func TestWupEncodeDecodeHeader(t *testing.T) {
t.Fatal("missing tReq")
}
}
// TestResponseDiagnoseIncludesServerFields 验证诊断串会把 WUP 响应里的排障字段带出来。
//
// 2026-10-16 的复盘卡在「响应缺少 tRsp/tResp」只有一句文案:服务端回的 iRet、
// status、context 与数据体键名全被丢掉,同一账号连续 13 次命中也无法定性。
func TestResponseDiagnoseIncludesServerFields(t *testing.T) {
resp := &wup.Response{
Message: -1,
Servant: "webActUI",
Func: "scoreExchangePrize",
Status: map[string]string{"STATUS_RESULT_DESC": "风控拦截", "STATUS_RESULT_CODE": "-1"},
Context: map[string]string{"uid": "123"},
NewData: map[string][]byte{"tErr": {0x0c}},
Buffer: []byte{1, 2, 3},
}
got := resp.Diagnose()
for _, want := range []string{
"iRet=-1",
"servant=webActUI.scoreExchangePrize",
"STATUS_RESULT_CODE=-1",
"STATUS_RESULT_DESC=风控拦截",
"context={uid=123}",
"keys={tErr(1B)}",
"payload=3B",
} {
if !strings.Contains(got, want) {
t.Fatalf("Diagnose()=%q,缺少 %q", got, want)
}
}
if got := (*wup.Response)(nil).Diagnose(); got != "响应为空" {
t.Fatalf("空响应诊断=%q", got)
}
if got := (&wup.Response{}).Diagnose(); !strings.Contains(got, "iRet=0") || !strings.Contains(got, "keys=空") {
t.Fatalf("零值响应诊断=%q", got)
}
}
// TestHexHead 验证原始帧前缀输出的截断与边界。
func TestHexHead(t *testing.T) {
data := []byte{0x1f, 0x8b, 0x08, 0x00}
if got := wup.HexHead(data, 2); got != "1f8b" {
t.Fatalf("HexHead(2)=%q", got)
}
if got := wup.HexHead(data, 32); got != "1f8b0800" {
t.Fatalf("HexHead 越界应截断到实际长度: %q", got)
}
if got := wup.HexHead(nil, 8); got != "" {
t.Fatalf("空数据=%q", got)
}
if got := wup.HexHead(data, 0); got != "" {
t.Fatalf("长度为 0=%q", got)
}
}