217 lines
7.4 KiB
Go
217 lines
7.4 KiB
Go
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
|
||
}
|