258 lines
7.4 KiB
Go
258 lines
7.4 KiB
Go
package gameprovider
|
|
|
|
import (
|
|
"chatapp3-golang/internal/common"
|
|
"context"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Registry 保存所有已注册的游戏厂商。
|
|
type Registry struct {
|
|
providers map[string]Provider
|
|
ordered []Provider
|
|
}
|
|
|
|
var emptyGameListUserIDs = map[int64]struct{}{
|
|
2061394478798794754: {},
|
|
}
|
|
|
|
// NewRegistry 创建厂商注册表。
|
|
func NewRegistry(providers ...Provider) *Registry {
|
|
r := &Registry{
|
|
providers: make(map[string]Provider, len(providers)),
|
|
ordered: make([]Provider, 0, len(providers)),
|
|
}
|
|
for _, provider := range providers {
|
|
r.Register(provider)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// Register 注册一个游戏厂商。
|
|
func (r *Registry) Register(provider Provider) {
|
|
if provider == nil {
|
|
return
|
|
}
|
|
key := normalizeKey(provider.Key())
|
|
if key == "" {
|
|
panic("gameprovider: empty provider key")
|
|
}
|
|
if _, exists := r.providers[key]; exists {
|
|
panic("gameprovider: duplicate provider key " + key)
|
|
}
|
|
r.providers[key] = provider
|
|
r.ordered = append(r.ordered, provider)
|
|
}
|
|
|
|
// Resolve 根据名称解析厂商,名称大小写不敏感。
|
|
func (r *Registry) Resolve(key string) (Provider, error) {
|
|
if r == nil {
|
|
return nil, common.NewAppError(http.StatusNotFound, "provider_not_found", "game provider not found")
|
|
}
|
|
normalizedKey := normalizeKey(key)
|
|
provider, ok := r.providers[normalizedKey]
|
|
if !ok {
|
|
provider, ok = r.providers[normalizeProviderAlias(normalizedKey)]
|
|
}
|
|
if !ok {
|
|
return nil, common.NewAppError(http.StatusNotFound, "provider_not_found", "game provider not found")
|
|
}
|
|
return provider, nil
|
|
}
|
|
|
|
// List 返回已注册厂商列表。
|
|
func (r *Registry) List() ProviderListResponse {
|
|
if r == nil || len(r.ordered) == 0 {
|
|
return ProviderListResponse{Items: []ProviderSummary{}}
|
|
}
|
|
items := make([]ProviderSummary, 0, len(r.ordered))
|
|
for _, provider := range r.ordered {
|
|
items = append(items, ProviderSummary{
|
|
Key: provider.Key(),
|
|
DisplayName: provider.DisplayName(),
|
|
})
|
|
}
|
|
return ProviderListResponse{Items: items}
|
|
}
|
|
|
|
// ListShortcutGames 返回聚合后的快捷游戏列表。
|
|
func (r *Registry) ListShortcutGames(ctx context.Context, user AuthUser, roomID string) ([]RoomGameListItem, error) {
|
|
if isEmptyGameListUser(user.UserID) {
|
|
return []RoomGameListItem{}, nil
|
|
}
|
|
if r == nil || len(r.ordered) == 0 {
|
|
return []RoomGameListItem{}, nil
|
|
}
|
|
|
|
items := make([]RoomGameListItem, 0, len(r.ordered))
|
|
for _, provider := range r.ordered {
|
|
resp, err := provider.ListShortcutGames(ctx, user, roomID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, resp...)
|
|
}
|
|
sortRoomGameListItems(items)
|
|
if len(items) > 5 {
|
|
items = items[:5]
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
// ListRoomGames 返回聚合后的房间游戏列表。
|
|
func (r *Registry) ListRoomGames(ctx context.Context, user AuthUser, roomID, category string) (*RoomGameListResponse, error) {
|
|
if isEmptyGameListUser(user.UserID) {
|
|
return &RoomGameListResponse{Items: []RoomGameListItem{}}, nil
|
|
}
|
|
if r == nil || len(r.ordered) == 0 {
|
|
return &RoomGameListResponse{Items: []RoomGameListItem{}}, nil
|
|
}
|
|
|
|
items := make([]RoomGameListItem, 0, len(r.ordered))
|
|
for _, provider := range r.ordered {
|
|
resp, err := provider.ListRoomGames(ctx, user, roomID, category)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp != nil {
|
|
items = append(items, resp.Items...)
|
|
}
|
|
}
|
|
sortRoomGameListItems(items)
|
|
return &RoomGameListResponse{Items: items}, nil
|
|
}
|
|
|
|
func isEmptyGameListUser(userID int64) bool {
|
|
_, ok := emptyGameListUserIDs[userID]
|
|
return ok
|
|
}
|
|
|
|
// ResolveByLaunchRequest 根据启动请求解析内部对应的厂商。
|
|
func (r *Registry) ResolveByLaunchRequest(ctx context.Context, user AuthUser, req LaunchRequest) (Provider, error) {
|
|
if r == nil {
|
|
return nil, common.NewAppError(http.StatusNotFound, "provider_not_found", "game provider not found")
|
|
}
|
|
if req.ID <= 0 && strings.TrimSpace(req.GameID) == "" {
|
|
return nil, common.NewAppError(http.StatusBadRequest, "missing_game_id", "id or gameId is required")
|
|
}
|
|
for _, provider := range r.ordered {
|
|
ok, err := provider.SupportsLaunch(ctx, user, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ok {
|
|
return provider, nil
|
|
}
|
|
}
|
|
return nil, common.NewAppError(http.StatusNotFound, "game_not_found", "game provider not found for gameId")
|
|
}
|
|
|
|
// GetRoomState 返回聚合后的房间状态。
|
|
func (r *Registry) GetRoomState(ctx context.Context, user AuthUser, roomID string) (*RoomStateResponse, error) {
|
|
if strings.TrimSpace(roomID) == "" {
|
|
return nil, common.NewAppError(http.StatusBadRequest, "missing_room_id", "roomId is required")
|
|
}
|
|
state, _, err := r.resolveProviderByRoomState(ctx, user, roomID, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if state != nil {
|
|
return state, nil
|
|
}
|
|
return &RoomStateResponse{RoomID: roomID, State: roomStateIdle}, nil
|
|
}
|
|
|
|
// LaunchGame 按 gameId 自动分发到内部对应的厂商实现。
|
|
func (r *Registry) LaunchGame(ctx context.Context, user AuthUser, req LaunchRequest, clientIP string) (*LaunchResponse, error) {
|
|
provider, err := r.ResolveByLaunchRequest(ctx, user, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return provider.LaunchGame(ctx, user, req, clientIP)
|
|
}
|
|
|
|
// CloseGame 根据当前房间状态自动路由到真实厂商。
|
|
func (r *Registry) CloseGame(ctx context.Context, user AuthUser, req CloseRequest) (*RoomStateResponse, error) {
|
|
if strings.TrimSpace(req.RoomID) == "" {
|
|
return nil, common.NewAppError(http.StatusBadRequest, "missing_room_id", "roomId is required")
|
|
}
|
|
_, provider, err := r.resolveProviderByRoomState(ctx, user, req.RoomID, req.GameSessionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if provider == nil {
|
|
return &RoomStateResponse{RoomID: req.RoomID, State: roomStateIdle}, nil
|
|
}
|
|
return provider.CloseGame(ctx, user, req)
|
|
}
|
|
|
|
const roomStateIdle = "IDLE"
|
|
|
|
func (r *Registry) resolveProviderByRoomState(ctx context.Context, user AuthUser, roomID, gameSessionID string) (*RoomStateResponse, Provider, error) {
|
|
if r == nil {
|
|
return nil, nil, common.NewAppError(http.StatusNotFound, "provider_not_found", "game provider not found")
|
|
}
|
|
|
|
sessionID := strings.TrimSpace(gameSessionID)
|
|
var activeState *RoomStateResponse
|
|
var activeProvider Provider
|
|
for _, provider := range r.ordered {
|
|
state, err := provider.GetRoomState(ctx, user, roomID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if state == nil {
|
|
continue
|
|
}
|
|
if sessionID != "" && strings.EqualFold(strings.TrimSpace(state.GameSessionID), sessionID) {
|
|
return state, provider, nil
|
|
}
|
|
if !isIdleRoomState(state) && activeProvider == nil {
|
|
activeState = state
|
|
activeProvider = provider
|
|
}
|
|
}
|
|
return activeState, activeProvider, nil
|
|
}
|
|
|
|
func isIdleRoomState(state *RoomStateResponse) bool {
|
|
if state == nil {
|
|
return true
|
|
}
|
|
if strings.TrimSpace(state.GameSessionID) != "" {
|
|
return false
|
|
}
|
|
value := strings.ToUpper(strings.TrimSpace(state.State))
|
|
if value == "" {
|
|
value = roomStateIdle
|
|
}
|
|
return value == roomStateIdle
|
|
}
|
|
|
|
func sortRoomGameListItems(items []RoomGameListItem) {
|
|
sort.SliceStable(items, func(i, j int) bool {
|
|
if items[i].Sort != items[j].Sort {
|
|
return items[i].Sort > items[j].Sort
|
|
}
|
|
if items[i].GameID != items[j].GameID {
|
|
return items[i].GameID < items[j].GameID
|
|
}
|
|
return strings.ToUpper(strings.TrimSpace(items[i].GameType)) < strings.ToUpper(strings.TrimSpace(items[j].GameType))
|
|
})
|
|
}
|
|
|
|
func normalizeKey(key string) string {
|
|
return strings.ToUpper(strings.TrimSpace(key))
|
|
}
|
|
|
|
func normalizeProviderAlias(key string) string {
|
|
switch normalizeKey(key) {
|
|
case "LEADER":
|
|
return "LINGXIAN"
|
|
default:
|
|
return normalizeKey(key)
|
|
}
|
|
}
|