2026-05-12 19:34:47 +08:00

334 lines
12 KiB
Go

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