334 lines
12 KiB
Go
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 ""
|
|
}
|
|
}
|