2026-06-08 19:00:23 +08:00

217 lines
7.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package gameapi
import (
"io"
"net/http"
"strings"
"time"
gamev1 "hyapp.local/api/proto/game/v1"
userv1 "hyapp.local/api/proto/user/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
"hyapp/services/gateway-service/internal/auth"
"hyapp/services/gateway-service/internal/transport/http/httpkit"
)
func (h *Handler) listGames(writer http.ResponseWriter, request *http.Request) {
if h.gameClient == nil || h.userProfileClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
userID := auth.UserIDFromContext(request.Context())
userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
Meta: httpkit.UserMeta(request, ""),
UserId: userID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
resp, err := h.gameClient.ListGames(request.Context(), &gamev1.ListGamesRequest{
Meta: gameMeta(request),
UserId: userID,
Scene: strings.TrimSpace(request.URL.Query().Get("scene")),
RoomId: strings.TrimSpace(request.URL.Query().Get("room_id")),
RegionId: userResp.GetUser().GetRegionId(),
Language: requestLanguage(request),
ClientPlatform: httpkit.FirstHeader(request, "X-App-Platform", "X-Platform"),
})
httpkit.Write(writer, request, gameListDataFromProto(resp), err)
}
func (h *Handler) listRecentGames(writer http.ResponseWriter, request *http.Request) {
if h.gameClient == nil || h.userProfileClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
pageSize, ok := recentGamePageSize(request)
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
userID := auth.UserIDFromContext(request.Context())
userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
Meta: httpkit.UserMeta(request, ""),
UserId: userID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
// 最近常玩仍传 scene、room、region、language 和 platform让 game-service 在真实启动记录之外,
// 继续按当前房间场景和用户区域过滤,避免 App 展示用户已不能进入的游戏。
resp, err := h.gameClient.ListRecentGames(request.Context(), &gamev1.ListRecentGamesRequest{
Meta: gameMeta(request),
UserId: userID,
Scene: strings.TrimSpace(request.URL.Query().Get("scene")),
RoomId: strings.TrimSpace(request.URL.Query().Get("room_id")),
RegionId: userResp.GetUser().GetRegionId(),
Language: requestLanguage(request),
ClientPlatform: httpkit.FirstHeader(request, "X-App-Platform", "X-Platform"),
PageSize: pageSize,
})
httpkit.Write(writer, request, gameListDataFromProto(resp), err)
}
func (h *Handler) getBridgeScript(writer http.ResponseWriter, request *http.Request) {
if h.gameClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
// 桥接脚本配置是 App 级启动预热信息,不依赖房间和用户区域;保持接口轻量,避免拖慢进 App 主流程。
resp, err := h.gameClient.GetBridgeScript(request.Context(), &gamev1.GetBridgeScriptRequest{
Meta: gameMeta(request),
})
httpkit.Write(writer, request, gameBridgeScriptDataFromProto(resp), err)
}
func (h *Handler) launchGame(writer http.ResponseWriter, request *http.Request) {
if h.gameClient == nil || h.userProfileClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
gameID := strings.TrimSpace(request.PathValue("game_id"))
if gameID == "" {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
var body struct {
Scene string `json:"scene"`
RoomID string `json:"room_id"`
}
if !httpkit.Decode(writer, request, &body) {
return
}
userID := auth.UserIDFromContext(request.Context())
userResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
Meta: httpkit.UserMeta(request, ""),
UserId: userID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
resp, err := h.gameClient.LaunchGame(request.Context(), &gamev1.LaunchGameRequest{
Meta: gameMeta(request),
UserId: userID,
DisplayUserId: userResp.GetUser().GetDisplayUserId(),
GameId: gameID,
Scene: body.Scene,
RoomId: body.RoomID,
AccessToken: bearerAccessToken(request),
})
httpkit.Write(writer, request, gameLaunchDataFromProto(resp), err)
}
func (h *Handler) handleGameCallback(writer http.ResponseWriter, request *http.Request) {
if h.gameClient == nil {
http.Error(writer, "upstream service error", http.StatusBadGateway)
return
}
raw, err := io.ReadAll(request.Body)
if err != nil {
http.Error(writer, "invalid request body", http.StatusBadRequest)
return
}
resp, err := h.gameClient.HandleCallback(request.Context(), &gamev1.CallbackRequest{
Meta: gameMeta(request),
PlatformCode: strings.TrimSpace(request.PathValue("platform_code")),
Operation: strings.TrimSpace(request.PathValue("operation")),
RawBody: raw,
Headers: flattenedHeaders(request),
Query: flattenedQuery(request),
RemoteAddr: request.RemoteAddr,
})
if err != nil {
status := xerr.SpecOf(xerr.ReasonFromGRPC(err)).HTTPStatus
http.Error(writer, xerr.SpecOf(xerr.ReasonFromGRPC(err)).PublicMessage, status)
return
}
contentType := strings.TrimSpace(resp.GetContentType())
if contentType == "" {
contentType = "application/json"
}
writer.Header().Set("Content-Type", contentType)
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write(resp.GetRawBody())
}
func gameMeta(request *http.Request) *gamev1.RequestMeta {
return &gamev1.RequestMeta{
RequestId: httpkit.RequestIDFromContext(request.Context()),
Caller: "gateway-service",
GatewayNodeId: "",
ActorUserId: auth.UserIDFromContext(request.Context()),
SentAtMs: time.Now().UnixMilli(),
AppCode: appcode.FromContext(request.Context()),
}
}
func requestLanguage(request *http.Request) string {
value := httpkit.FirstHeader(request, "X-App-Language", "X-Language")
if value == "" {
value = request.Header.Get("Accept-Language")
}
if index := strings.Index(value, ","); index >= 0 {
value = value[:index]
}
return strings.TrimSpace(value)
}
func recentGamePageSize(request *http.Request) (int32, bool) {
if strings.TrimSpace(request.URL.Query().Get("limit")) != "" {
return httpkit.PositiveInt32Query(request, "limit", 4)
}
return httpkit.PositiveInt32Query(request, "page_size", 4)
}
func bearerAccessToken(request *http.Request) string {
header := strings.TrimSpace(request.Header.Get("Authorization"))
if !strings.HasPrefix(header, "Bearer ") {
return ""
}
return strings.TrimSpace(strings.TrimPrefix(header, "Bearer "))
}
func flattenedHeaders(request *http.Request) map[string]string {
headers := make(map[string]string, len(request.Header))
for key, values := range request.Header {
if len(values) > 0 {
headers[key] = values[0]
}
}
return headers
}
func flattenedQuery(request *http.Request) map[string]string {
query := request.URL.Query()
result := make(map[string]string, len(query))
for key, values := range query {
if len(values) > 0 {
result[key] = values[0]
}
}
return result
}