2026-06-02 20:49:58 +08:00

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)
}
}