From 4024f62e53a96555b275bacc2a5832e3e69b7d75 Mon Sep 17 00:00:00 2001 From: zhx Date: Fri, 12 Jun 2026 20:09:17 +0800 Subject: [PATCH] fix: proxy reyou callbacks with chatapp3 token --- .../transport/http/game_handler_test.go | 48 +++++++++++++++++++ .../transport/http/gameapi/game_handler.go | 9 +++- .../http/gameapi/hotgame_compat_handler.go | 15 ++++-- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/services/gateway-service/internal/transport/http/game_handler_test.go b/services/gateway-service/internal/transport/http/game_handler_test.go index c9d1aeeb..9ea76209 100644 --- a/services/gateway-service/internal/transport/http/game_handler_test.go +++ b/services/gateway-service/internal/transport/http/game_handler_test.go @@ -623,6 +623,54 @@ func TestHotgameCompatProxiesChatapp3TokenWithoutGameCallback(t *testing.T) { } } +func TestReyouGameCallbackProxiesChatapp3TokenWithoutGameCallback(t *testing.T) { + token := chatapp3HotgameToken("v1", 99123456, "LIKEI", 4102444800000, 1700000000000) + body := `{"gameId":"20","uid":"99123456","token":"` + token + `","sign":"s"}` + var gotHost string + var gotPath string + var gotQuery string + var gotBody string + var gotContentType string + oldTransport := http.DefaultTransport + http.DefaultTransport = roundTripFunc(func(request *http.Request) (*http.Response, error) { + gotHost = request.URL.Host + gotPath = request.URL.Path + gotQuery = request.URL.RawQuery + gotContentType = request.Header.Get("Content-Type") + raw, _ := io.ReadAll(request.Body) + gotBody = string(raw) + header := http.Header{} + header.Set("Content-Type", "application/json") + return &http.Response{ + StatusCode: http.StatusOK, + Header: header, + Body: io.NopCloser(bytes.NewReader([]byte(`{"errorCode":0,"data":{"coin":1000}}`))), + Request: request, + }, nil + }) + defer func() { http.DefaultTransport = oldTransport }() + gameClient := &fakeGatewayGameClient{callbackResp: &gamev1.CallbackResponse{RawBody: []byte(`{"errorCode":0}`), ContentType: "application/json"}} + handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) + handler.SetGameClient(gameClient) + router := handler.Routes(auth.NewVerifier("secret")) + request := httptest.NewRequest(http.MethodPost, "/api/v1/game-callbacks/reyou/getUserInfo?nonce=1", bytes.NewReader([]byte(body))) + request.Header.Set("X-App-Code", "lalu") + request.Header.Set("Content-Type", "application/json") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK || recorder.Body.String() != `{"errorCode":0,"data":{"coin":1000}}` { + t.Fatalf("reyou chatapp3 proxy response mismatch: status=%d body=%s", recorder.Code, recorder.Body.String()) + } + if gotHost != "jvapi.haiyihy.com" || gotPath != "/go/getUserInfo" || gotQuery != "nonce=1" || gotBody != body || gotContentType != "application/json" { + t.Fatalf("reyou chatapp3 proxy request mismatch: host=%q path=%q query=%q content_type=%q body=%s", gotHost, gotPath, gotQuery, gotContentType, gotBody) + } + if gameClient.lastCallback != nil { + t.Fatalf("reyou chatapp3 token must not enter hyapp game callback: %+v", gameClient.lastCallback) + } +} + func chatapp3HotgameToken(version string, userID int64, sysOrigin string, expireMs int64, releaseMs int64) string { payload := url.QueryEscape(version + ":" + strconv.FormatInt(userID, 10) + ":" + sysOrigin + ":" + strconv.FormatInt(expireMs, 10) + ":" + strconv.FormatInt(releaseMs, 10)) return "sign." + base64.StdEncoding.EncodeToString([]byte(payload)) diff --git a/services/gateway-service/internal/transport/http/gameapi/game_handler.go b/services/gateway-service/internal/transport/http/gameapi/game_handler.go index 61875e42..0566c8b5 100644 --- a/services/gateway-service/internal/transport/http/gameapi/game_handler.go +++ b/services/gateway-service/internal/transport/http/gameapi/game_handler.go @@ -750,7 +750,14 @@ func (h *Handler) handleGameCallback(writer http.ResponseWriter, request *http.R http.Error(writer, "invalid request body", http.StatusBadRequest) return } - h.handleGameCallbackRaw(writer, request, raw, strings.TrimSpace(request.PathValue("platform_code")), strings.TrimSpace(request.PathValue("operation"))) + platformCode := strings.TrimSpace(request.PathValue("platform_code")) + operation := strings.TrimSpace(request.PathValue("operation")) + // reyou 的线上回调现在存在两种入口:根路径兼容入口和 /game-callbacks/reyou/* 通用入口。 + // 通用入口同样先按 token 归属做分流,chatapp3 token 原样回源 chatapp3,其他 token 才交给 hyapp 自己的游戏服务。 + if h.tryProxyChatapp3HotgameCallback(writer, request, raw, platformCode, operation) { + return + } + h.handleGameCallbackRaw(writer, request, raw, platformCode, operation) } func (h *Handler) handleGameCallbackRaw(writer http.ResponseWriter, request *http.Request, raw []byte, platformCode string, operation string) { diff --git a/services/gateway-service/internal/transport/http/gameapi/hotgame_compat_handler.go b/services/gateway-service/internal/transport/http/gameapi/hotgame_compat_handler.go index 830833ca..bd9a5854 100644 --- a/services/gateway-service/internal/transport/http/gameapi/hotgame_compat_handler.go +++ b/services/gateway-service/internal/transport/http/gameapi/hotgame_compat_handler.go @@ -35,16 +35,23 @@ func (h *Handler) handleHotgameCompatCallback(writer http.ResponseWriter, reques http.Error(writer, "invalid request body", http.StatusBadRequest) return } - // 热游固定回调地址无法按域名区分 hyapp 和 chatapp3;这里用 token 结构做最小路由键。 - // chatapp3 的用户 token 是 sign.base64(version:userId:sysOrigin:expire:release),hyapp 当前热游 token 是 JWT 或本服务 session。 - if isChatapp3HotgameToken(hotgameCompatCallbackToken(raw)) { - h.proxyChatapp3HotgameCallback(writer, request, raw, operation) + if h.tryProxyChatapp3HotgameCallback(writer, request, raw, hotgameCompatPlatformCode, operation) { return } // 非 chatapp3 token 全部继续走 hyapp 自己的 reyou 平台配置,避免 hyapp 自有热游游戏误转到 chatapp3。 h.handleGameCallbackRaw(writer, request, raw, hotgameCompatPlatformCode, operation) } +func (h *Handler) tryProxyChatapp3HotgameCallback(writer http.ResponseWriter, request *http.Request, raw []byte, platformCode string, operation string) bool { + // 热游固定回调地址无法按域名区分 hyapp 和 chatapp3;这里用 token 结构做最小路由键。 + // chatapp3 的用户 token 是 sign.base64(version:userId:sysOrigin:expire:release),hyapp 当前热游 token 是 JWT 或本服务 session。 + if !strings.EqualFold(strings.TrimSpace(platformCode), hotgameCompatPlatformCode) || !isChatapp3HotgameToken(hotgameCompatCallbackToken(raw)) { + return false + } + h.proxyChatapp3HotgameCallback(writer, request, raw, operation) + return true +} + func (h *Handler) proxyChatapp3HotgameCallback(writer http.ResponseWriter, request *http.Request, raw []byte, operation string) { target, err := hotgameCompatProxyURL(operation, request.URL.RawQuery) if err != nil {