yumi-golang/internal/router/game_provider_routes.go
2026-06-12 19:37:04 +08:00

404 lines
13 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 router
import (
"errors"
"net/http"
"strings"
"chatapp3-golang/internal/common"
"chatapp3-golang/internal/service/gameprovider"
"github.com/gin-gonic/gin"
)
// registerGameProviderRoutes 注册统一游戏厂商 app 路由。
func registerGameProviderRoutes(engine *gin.Engine, javaClient authGateway, registry *gameprovider.Registry, visibility *gameprovider.VisibilityPolicy) {
if registry == nil {
return
}
appGroup := engine.Group("/app/game")
appGroup.Use(authMiddleware(javaClient))
appGroup.GET("/room/shortcut", func(c *gin.Context) {
user, visible, ok := gameListAccess(c, visibility)
if !ok {
return
}
if !visible {
writeOK(c, []publicRoomGameItem{})
return
}
resp, err := registry.ListShortcutGames(c.Request.Context(), user, c.Query("roomId"))
if err != nil {
writeError(c, err)
return
}
writeOK(c, publicRoomGameItems(resp))
})
appGroup.GET("/room/list", func(c *gin.Context) {
user, visible, ok := gameListAccess(c, visibility)
if !ok {
return
}
if !visible {
writeOK(c, publicRoomGameListResponse(&gameprovider.RoomGameListResponse{Items: []gameprovider.RoomGameListItem{}}))
return
}
resp, err := registry.ListRoomGames(c.Request.Context(), user, c.Query("roomId"), c.Query("category"))
if err != nil {
writeError(c, err)
return
}
writeOK(c, publicRoomGameListResponse(resp))
})
appGroup.GET("/state", func(c *gin.Context) {
resp, err := registry.GetRoomState(c.Request.Context(), mustAuthUser(c), c.Query("roomId"))
if err != nil {
writeError(c, err)
return
}
writeOK(c, publicRoomState(resp))
})
appGroup.POST("/launch", func(c *gin.Context) {
var req gameprovider.LaunchRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, common.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := registry.LaunchGame(c.Request.Context(), mustAuthUser(c), req, resolveClientIP(c))
if err != nil {
writeError(c, err)
return
}
writeOK(c, publicLaunch(resp))
})
appGroup.POST("/close", func(c *gin.Context) {
var req gameprovider.CloseRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, common.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := registry.CloseGame(c.Request.Context(), mustAuthUser(c), req)
if err != nil {
writeError(c, err)
return
}
writeOK(c, publicRoomState(resp))
})
providerGroup := engine.Group("/app/game/providers")
providerGroup.Use(authMiddleware(javaClient))
providerGroup.GET("", func(c *gin.Context) {
_, visible, ok := gameListAccess(c, visibility)
if !ok {
return
}
if !visible {
writeOK(c, gameprovider.ProviderListResponse{Items: []gameprovider.ProviderSummary{}})
return
}
writeOK(c, registry.List())
})
providerGroup.GET("/:provider/room/shortcut", func(c *gin.Context) {
user, visible, ok := gameListAccess(c, visibility)
if !ok {
return
}
if !visible {
writeOK(c, gin.H{"items": []publicRoomGameItem{}})
return
}
provider, ok := resolveGameProvider(c, registry)
if !ok {
return
}
resp, err := provider.ListShortcutGames(c.Request.Context(), user, c.Query("roomId"))
if err != nil {
writeError(c, err)
return
}
writeOK(c, gin.H{"items": publicRoomGameItems(resp)})
})
providerGroup.GET("/:provider/room/list", func(c *gin.Context) {
user, visible, ok := gameListAccess(c, visibility)
if !ok {
return
}
if !visible {
writeOK(c, publicRoomGameListResponse(&gameprovider.RoomGameListResponse{Items: []gameprovider.RoomGameListItem{}}))
return
}
provider, ok := resolveGameProvider(c, registry)
if !ok {
return
}
resp, err := provider.ListRoomGames(c.Request.Context(), user, c.Query("roomId"), c.Query("category"))
if err != nil {
writeError(c, err)
return
}
writeOK(c, publicRoomGameListResponse(resp))
})
providerGroup.GET("/:provider/state", func(c *gin.Context) {
provider, ok := resolveGameProvider(c, registry)
if !ok {
return
}
resp, err := provider.GetRoomState(c.Request.Context(), mustAuthUser(c), c.Query("roomId"))
if err != nil {
writeError(c, err)
return
}
writeOK(c, publicRoomState(resp))
})
providerGroup.POST("/:provider/launch", func(c *gin.Context) {
provider, ok := resolveGameProvider(c, registry)
if !ok {
return
}
var req gameprovider.LaunchRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, common.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := launchProviderGame(c, registry, provider, req)
if err != nil {
writeError(c, err)
return
}
writeOK(c, publicLaunch(resp))
})
providerGroup.POST("/:provider/close", func(c *gin.Context) {
provider, ok := resolveGameProvider(c, registry)
if !ok {
return
}
var req gameprovider.CloseRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, common.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := provider.CloseGame(c.Request.Context(), mustAuthUser(c), req)
if err != nil {
writeError(c, err)
return
}
writeOK(c, publicRoomState(resp))
})
}
func gameListAccess(c *gin.Context, visibility *gameprovider.VisibilityPolicy) (common.AuthUser, bool, bool) {
user := mustAuthUser(c)
if gameprovider.IsEmptyGameListUser(user.UserID) {
return user, false, true
}
if visibility == nil {
return user, true, true
}
// 游戏列表只在列表入口做展示门禁,结果里的区域继续向下传给 provider SQL 做 regions 过滤。
result, err := visibility.Evaluate(c.Request.Context(), user, resolveClientIP(c))
if err != nil {
writeError(c, err)
return user, false, false
}
user.RegionID = result.RegionID
user.RegionCode = result.RegionCode
return user, result.Allow, true
}
func resolveGameProvider(c *gin.Context, registry *gameprovider.Registry) (gameprovider.Provider, bool) {
provider, err := registry.Resolve(c.Param("provider"))
if err != nil {
writeError(c, err)
return nil, false
}
return provider, true
}
func launchProviderGame(c *gin.Context, registry *gameprovider.Registry, provider gameprovider.Provider, req gameprovider.LaunchRequest) (*gameprovider.LaunchResponse, error) {
user := mustAuthUser(c)
clientIP := resolveClientIP(c)
resp, err := provider.LaunchGame(c.Request.Context(), user, req, clientIP)
if err == nil {
return resp, nil
}
if !isGameNotFoundError(err) {
return nil, err
}
// 旧客户端只能把热游列表项伪装成 LEADER 进入已有 WebView这里再按 gameId 回到真实 provider避免 LEADER 路由找不到 hg_*。
return registry.LaunchGame(c.Request.Context(), user, req, clientIP)
}
func isGameNotFoundError(err error) bool {
var appErr *common.AppError
return errors.As(err, &appErr) && appErr.Code == "game_not_found"
}
type publicRoomGameItem struct {
ID int64 `json:"id"`
GameID string `json:"gameId"`
GameType string `json:"gameType,omitempty"`
Provider string `json:"provider,omitempty"`
VendorType string `json:"vendorType,omitempty"`
ProviderGameID string `json:"providerGameId,omitempty"`
VendorGameID string `json:"vendorGameId,omitempty"`
Name string `json:"name"`
Cover string `json:"cover"`
Category string `json:"category,omitempty"`
Sort int64 `json:"sort,omitempty"`
LaunchMode string `json:"launchMode,omitempty"`
FullScreen bool `json:"fullScreen"`
GameMode int `json:"gameMode,omitempty"`
SafeHeight int `json:"safeHeight,omitempty"`
Orientation int `json:"orientation,omitempty"`
PackageVersion string `json:"packageVersion,omitempty"`
Status string `json:"status,omitempty"`
LaunchParams map[string]any `json:"launchParams,omitempty"`
}
type publicRoomGameList struct {
Items []publicRoomGameItem `json:"items"`
}
type publicRoomStateResponse struct {
RoomID string `json:"roomId"`
State string `json:"state"`
Provider string `json:"provider,omitempty"`
VendorType string `json:"vendorType,omitempty"`
GameSessionID string `json:"gameSessionId,omitempty"`
CurrentGameID string `json:"currentGameId,omitempty"`
CurrentProviderGameID string `json:"currentProviderGameId,omitempty"`
CurrentVendorGameID string `json:"currentVendorGameId,omitempty"`
CurrentGameName string `json:"currentGameName,omitempty"`
CurrentGameCover string `json:"currentGameCover,omitempty"`
HostUserID int64 `json:"hostUserId,omitempty"`
}
type publicLaunchResponse struct {
ID int64 `json:"id,omitempty"`
GameSessionID string `json:"gameSessionId"`
Provider string `json:"provider,omitempty"`
VendorType string `json:"vendorType,omitempty"`
GameID string `json:"gameId"`
ProviderGameID string `json:"providerGameId,omitempty"`
VendorGameID string `json:"vendorGameId,omitempty"`
Entry gameprovider.LaunchEntry `json:"entry"`
LaunchConfig any `json:"launchConfig,omitempty"`
BridgeConfig any `json:"bridgeConfig,omitempty"`
RoomState publicRoomStateResponse `json:"roomState"`
}
func publicRoomGameItems(items []gameprovider.RoomGameListItem) []publicRoomGameItem {
if len(items) == 0 {
return []publicRoomGameItem{}
}
result := make([]publicRoomGameItem, 0, len(items))
for _, item := range items {
provider := appCompatProviderName(item.Provider)
gameType := appCompatProviderName(item.GameType)
result = append(result, publicRoomGameItem{
ID: item.ID,
GameID: item.GameID,
GameType: gameType,
Provider: provider,
VendorType: provider,
ProviderGameID: item.ProviderGameID,
VendorGameID: item.ProviderGameID,
Name: item.Name,
Cover: item.Cover,
Category: item.Category,
Sort: item.Sort,
LaunchMode: item.LaunchMode,
FullScreen: item.FullScreen,
GameMode: item.GameMode,
SafeHeight: item.SafeHeight,
Orientation: item.Orientation,
PackageVersion: item.PackageVersion,
Status: item.Status,
LaunchParams: publicLaunchParams(item.LaunchParams),
})
}
return result
}
func publicLaunchParams(params map[string]any) map[string]any {
if len(params) == 0 {
return nil
}
result := make(map[string]any, len(params))
for key, value := range params {
if strings.EqualFold(strings.TrimSpace(key), "gameType") {
if text, ok := value.(string); ok {
result[key] = appCompatProviderName(text)
continue
}
}
result[key] = value
}
return result
}
func publicRoomGameListResponse(resp *gameprovider.RoomGameListResponse) *publicRoomGameList {
if resp == nil {
return &publicRoomGameList{Items: []publicRoomGameItem{}}
}
return &publicRoomGameList{
Items: publicRoomGameItems(resp.Items),
}
}
func publicRoomState(resp *gameprovider.RoomStateResponse) *publicRoomStateResponse {
if resp == nil {
return nil
}
return &publicRoomStateResponse{
RoomID: resp.RoomID,
State: resp.State,
Provider: appCompatProviderName(resp.Provider),
VendorType: appCompatProviderName(resp.Provider),
GameSessionID: resp.GameSessionID,
CurrentGameID: resp.CurrentGameID,
CurrentProviderGameID: resp.CurrentProviderGameID,
CurrentVendorGameID: resp.CurrentProviderGameID,
CurrentGameName: resp.CurrentGameName,
CurrentGameCover: resp.CurrentGameCover,
HostUserID: resp.HostUserID,
}
}
func publicLaunch(resp *gameprovider.LaunchResponse) *publicLaunchResponse {
if resp == nil {
return nil
}
roomState := publicRoomState(&resp.RoomState)
if roomState == nil {
roomState = &publicRoomStateResponse{}
}
return &publicLaunchResponse{
ID: resp.ID,
GameSessionID: resp.GameSessionID,
Provider: appCompatProviderName(resp.Provider),
VendorType: appCompatProviderName(resp.Provider),
GameID: resp.GameID,
ProviderGameID: resp.ProviderGameID,
VendorGameID: resp.ProviderGameID,
Entry: resp.Entry,
LaunchConfig: resp.LaunchConfig,
BridgeConfig: resp.LaunchConfig,
RoomState: *roomState,
}
}
func appCompatProviderName(provider string) string {
// 客户端历史上只认识 LEADER不认识内部厂商名 LINGXIAN所以出参继续保持旧字段值。
if strings.EqualFold(strings.TrimSpace(provider), "LINGXIAN") {
return "LEADER"
}
// 旧客户端没有 HOTGAME 页面;热游先按 LEADER 展示并进入已有 WebViewlaunch 路由再按 hg_* 分发回真实热游 provider。
switch strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(provider), "_", "")) {
case "HOTGAME", "REYOU", "LALU":
return "LEADER"
}
return provider
}