package grpc import ( "context" "strings" "time" gamev1 "hyapp.local/api/proto/game/v1" "hyapp/pkg/appcode" "hyapp/pkg/xerr" gamedomain "hyapp/services/game-service/internal/domain/game" gameservice "hyapp/services/game-service/internal/service/game" ) // Server 把 game-service 用例层适配为 gRPC 契约。 type Server struct { gamev1.UnimplementedGameAppServiceServer gamev1.UnimplementedGameCallbackServiceServer gamev1.UnimplementedGameAdminServiceServer svc *gameservice.Service } func NewServer(svc *gameservice.Service) *Server { return &Server{svc: svc} } func (s *Server) ListGames(ctx context.Context, req *gamev1.ListGamesRequest) (*gamev1.ListGamesResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) items, serverTimeMS, err := s.svc.ListGames(ctx, gameservice.ListGamesQuery{ AppCode: req.GetMeta().GetAppCode(), UserID: req.GetUserId(), Scene: req.GetScene(), RoomID: req.GetRoomId(), RegionID: req.GetRegionId(), Language: req.GetLanguage(), ClientPlatform: req.GetClientPlatform(), }) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &gamev1.ListGamesResponse{ServerTimeMs: serverTimeMS, Games: make([]*gamev1.AppGame, 0, len(items))} for _, item := range items { resp.Games = append(resp.Games, appGameToProto(item)) } return resp, nil } func (s *Server) LaunchGame(ctx context.Context, req *gamev1.LaunchGameRequest) (*gamev1.LaunchGameResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) result, err := s.svc.LaunchGame(ctx, gameservice.LaunchCommand{ AppCode: req.GetMeta().GetAppCode(), RequestID: req.GetMeta().GetRequestId(), UserID: req.GetUserId(), DisplayUserID: req.GetDisplayUserId(), GameID: req.GetGameId(), Scene: req.GetScene(), RoomID: req.GetRoomId(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &gamev1.LaunchGameResponse{ SessionId: result.SessionID, LaunchUrl: result.LaunchURL, ExpiresAtMs: result.ExpiresAtMS, Orientation: result.Orientation, ServerTimeMs: result.ServerTimeMS, }, nil } func (s *Server) HandleCallback(ctx context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) raw, contentType, err := s.svc.HandleCallback(ctx, req) if err != nil { return nil, xerr.ToGRPCError(err) } return &gamev1.CallbackResponse{RawBody: raw, ContentType: contentType}, nil } func (s *Server) ListPlatforms(ctx context.Context, req *gamev1.ListPlatformsRequest) (*gamev1.ListPlatformsResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) items, err := s.svcRepository().ListPlatforms(ctx, req.GetMeta().GetAppCode(), req.GetStatus()) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &gamev1.ListPlatformsResponse{ServerTimeMs: time.Now().UnixMilli(), Platforms: make([]*gamev1.GamePlatform, 0, len(items))} for _, item := range items { resp.Platforms = append(resp.Platforms, platformToProto(item)) } return resp, nil } func (s *Server) UpsertPlatform(ctx context.Context, req *gamev1.UpsertPlatformRequest) (*gamev1.PlatformResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) platform := platformFromProto(req.GetPlatform()) platform.AppCode = req.GetMeta().GetAppCode() normalized, err := normalizePlatform(platform) if err != nil { return nil, xerr.ToGRPCError(err) } saved, err := s.svcRepository().UpsertPlatform(ctx, normalized) if err != nil { return nil, xerr.ToGRPCError(err) } return &gamev1.PlatformResponse{Platform: platformToProto(saved), ServerTimeMs: time.Now().UnixMilli()}, nil } func (s *Server) ListCatalog(ctx context.Context, req *gamev1.ListCatalogRequest) (*gamev1.ListCatalogResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) items, next, err := s.svcRepository().ListCatalog(ctx, gameservice.ListCatalogQuery{ AppCode: req.GetMeta().GetAppCode(), PlatformCode: req.GetPlatformCode(), Status: req.GetStatus(), PageSize: req.GetPageSize(), Cursor: req.GetCursor(), }) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &gamev1.ListCatalogResponse{NextCursor: next, ServerTimeMs: time.Now().UnixMilli(), Games: make([]*gamev1.GameCatalogItem, 0, len(items))} for _, item := range items { resp.Games = append(resp.Games, catalogToProto(item)) } return resp, nil } func (s *Server) UpsertCatalog(ctx context.Context, req *gamev1.UpsertCatalogRequest) (*gamev1.CatalogResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) item := catalogFromProto(req.GetGame()) item.AppCode = req.GetMeta().GetAppCode() normalized, err := normalizeCatalog(item) if err != nil { return nil, xerr.ToGRPCError(err) } saved, err := s.svcRepository().UpsertCatalog(ctx, normalized) if err != nil { return nil, xerr.ToGRPCError(err) } return &gamev1.CatalogResponse{Game: catalogToProto(saved), ServerTimeMs: time.Now().UnixMilli()}, nil } func (s *Server) SetGameStatus(ctx context.Context, req *gamev1.SetGameStatusRequest) (*gamev1.CatalogResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) status := normalizeStatus(req.GetStatus()) if status == "" { return nil, xerr.ToGRPCError(xerr.New(xerr.InvalidArgument, "status is invalid")) } item, err := s.svcRepository().SetGameStatus(ctx, req.GetMeta().GetAppCode(), req.GetGameId(), status, time.Now().UnixMilli()) if err != nil { return nil, xerr.ToGRPCError(err) } return &gamev1.CatalogResponse{Game: catalogToProto(item), ServerTimeMs: time.Now().UnixMilli()}, nil } func (s *Server) svcRepository() gameservice.Repository { if s == nil || s.svc == nil || s.svc.Repository() == nil { return unavailableRepository{} } return s.svc.Repository() } type unavailableRepository struct{} func (unavailableRepository) Ping(context.Context) error { return xerr.New(xerr.Unavailable, "game repository is not configured") } func (unavailableRepository) ListGames(context.Context, gameservice.ListGamesQuery) ([]gamedomain.AppGame, error) { return nil, xerr.New(xerr.Unavailable, "game repository is not configured") } func (unavailableRepository) GetLaunchableGame(context.Context, string, string) (gamedomain.LaunchableGame, error) { return gamedomain.LaunchableGame{}, xerr.New(xerr.Unavailable, "game repository is not configured") } func (unavailableRepository) CreateLaunchSession(context.Context, gamedomain.LaunchSession) error { return xerr.New(xerr.Unavailable, "game repository is not configured") } func (unavailableRepository) InsertCallbackLog(context.Context, gamedomain.CallbackLog) error { return xerr.New(xerr.Unavailable, "game repository is not configured") } func (unavailableRepository) CreateGameOrder(context.Context, gamedomain.GameOrder) (gamedomain.GameOrder, bool, error) { return gamedomain.GameOrder{}, false, xerr.New(xerr.Unavailable, "game repository is not configured") } func (unavailableRepository) MarkOrderSucceeded(context.Context, string, string, string, int64, int64) error { return xerr.New(xerr.Unavailable, "game repository is not configured") } func (unavailableRepository) MarkOrderFailed(context.Context, string, string, string, string, string, int64) error { return xerr.New(xerr.Unavailable, "game repository is not configured") } func (unavailableRepository) ListPlatforms(context.Context, string, string) ([]gamedomain.Platform, error) { return nil, xerr.New(xerr.Unavailable, "game repository is not configured") } func (unavailableRepository) UpsertPlatform(context.Context, gamedomain.Platform) (gamedomain.Platform, error) { return gamedomain.Platform{}, xerr.New(xerr.Unavailable, "game repository is not configured") } func (unavailableRepository) ListCatalog(context.Context, gameservice.ListCatalogQuery) ([]gamedomain.CatalogItem, string, error) { return nil, "", xerr.New(xerr.Unavailable, "game repository is not configured") } func (unavailableRepository) UpsertCatalog(context.Context, gamedomain.CatalogItem) (gamedomain.CatalogItem, error) { return gamedomain.CatalogItem{}, xerr.New(xerr.Unavailable, "game repository is not configured") } func (unavailableRepository) SetGameStatus(context.Context, string, string, string, int64) (gamedomain.CatalogItem, error) { return gamedomain.CatalogItem{}, xerr.New(xerr.Unavailable, "game repository is not configured") } func appGameToProto(item gamedomain.AppGame) *gamev1.AppGame { return &gamev1.AppGame{ GameId: item.GameID, PlatformCode: item.PlatformCode, NameKey: item.NameKey, Name: item.Name, IconUrl: item.IconURL, CoverUrl: item.CoverURL, Category: item.Category, LaunchMode: item.LaunchMode, Orientation: item.Orientation, MinCoin: item.MinCoin, Enabled: item.Enabled, Maintenance: item.Maintenance, SortOrder: item.SortOrder, } } func platformToProto(item gamedomain.Platform) *gamev1.GamePlatform { return &gamev1.GamePlatform{ AppCode: item.AppCode, PlatformCode: item.PlatformCode, PlatformName: item.PlatformName, Status: item.Status, ApiBaseUrl: item.APIBaseURL, SortOrder: item.SortOrder, CreatedAtMs: item.CreatedAtMS, UpdatedAtMs: item.UpdatedAtMS, } } func platformFromProto(item *gamev1.GamePlatform) gamedomain.Platform { if item == nil { return gamedomain.Platform{} } return gamedomain.Platform{ AppCode: item.GetAppCode(), PlatformCode: item.GetPlatformCode(), PlatformName: item.GetPlatformName(), Status: item.GetStatus(), APIBaseURL: item.GetApiBaseUrl(), SortOrder: item.GetSortOrder(), } } func catalogToProto(item gamedomain.CatalogItem) *gamev1.GameCatalogItem { return &gamev1.GameCatalogItem{ AppCode: item.AppCode, GameId: item.GameID, PlatformCode: item.PlatformCode, ProviderGameId: item.ProviderGameID, GameName: item.GameName, Category: item.Category, IconUrl: item.IconURL, CoverUrl: item.CoverURL, LaunchMode: item.LaunchMode, Orientation: item.Orientation, MinCoin: item.MinCoin, Status: item.Status, SortOrder: item.SortOrder, Tags: item.Tags, CreatedAtMs: item.CreatedAtMS, UpdatedAtMs: item.UpdatedAtMS, } } func catalogFromProto(item *gamev1.GameCatalogItem) gamedomain.CatalogItem { if item == nil { return gamedomain.CatalogItem{} } return gamedomain.CatalogItem{ AppCode: item.GetAppCode(), GameID: item.GetGameId(), PlatformCode: item.GetPlatformCode(), ProviderGameID: item.GetProviderGameId(), GameName: item.GetGameName(), Category: item.GetCategory(), IconURL: item.GetIconUrl(), CoverURL: item.GetCoverUrl(), LaunchMode: item.GetLaunchMode(), Orientation: item.GetOrientation(), MinCoin: item.GetMinCoin(), Status: item.GetStatus(), SortOrder: item.GetSortOrder(), Tags: item.GetTags(), } } func normalizePlatform(item gamedomain.Platform) (gamedomain.Platform, error) { item.PlatformCode = strings.TrimSpace(item.PlatformCode) item.PlatformName = strings.TrimSpace(item.PlatformName) item.Status = normalizeStatus(item.Status) item.APIBaseURL = strings.TrimSpace(item.APIBaseURL) if item.PlatformCode == "" || item.PlatformName == "" || item.Status == "" { return gamedomain.Platform{}, xerr.New(xerr.InvalidArgument, "platform is incomplete") } return item, nil } func normalizeCatalog(item gamedomain.CatalogItem) (gamedomain.CatalogItem, error) { item.GameID = strings.TrimSpace(item.GameID) item.PlatformCode = strings.TrimSpace(item.PlatformCode) item.ProviderGameID = strings.TrimSpace(item.ProviderGameID) item.GameName = strings.TrimSpace(item.GameName) item.Status = normalizeStatus(item.Status) if item.LaunchMode == "" { item.LaunchMode = gamedomain.LaunchModeH5Popup } if item.Orientation == "" { item.Orientation = "portrait" } if item.GameID == "" || item.PlatformCode == "" || item.ProviderGameID == "" || item.GameName == "" || item.Status == "" { return gamedomain.CatalogItem{}, xerr.New(xerr.InvalidArgument, "game is incomplete") } return item, nil } func normalizeStatus(status string) string { switch strings.ToLower(strings.TrimSpace(status)) { case gamedomain.StatusActive: return gamedomain.StatusActive case gamedomain.StatusMaintenance: return gamedomain.StatusMaintenance case gamedomain.StatusDisabled: return gamedomain.StatusDisabled default: return "" } }