264 lines
8.7 KiB
Go
264 lines
8.7 KiB
Go
package baishun
|
|
|
|
import (
|
|
"chatapp3-golang/internal/integration"
|
|
"chatapp3-golang/internal/model"
|
|
"chatapp3-golang/internal/utils"
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// LaunchGame 创建会话、拉起百顺游戏并更新房间状态。
|
|
func (s *BaishunService) LaunchGame(ctx context.Context, user AuthUser, req BaishunLaunchAppRequest, clientIP string) (*BaishunLaunchAppResponse, error) {
|
|
if strings.TrimSpace(req.RoomID) == "" {
|
|
return nil, NewAppError(http.StatusBadRequest, "missing_room_id", "roomId is required")
|
|
}
|
|
if strings.TrimSpace(req.GameID) == "" {
|
|
return nil, NewAppError(http.StatusBadRequest, "missing_game_id", "gameId is required")
|
|
}
|
|
|
|
row, err := s.findGameRow(ctx, user.SysOrigin, req.GameID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sessionID, err := utils.NextID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sessionDBID, err := utils.NextID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
roomStateID, err := utils.NextID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
launchCode, err := s.randomToken(24)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
now := time.Now()
|
|
session := model.BaishunLaunchSession{
|
|
ID: sessionDBID,
|
|
SysOrigin: user.SysOrigin,
|
|
RoomID: req.RoomID,
|
|
UserID: user.UserID,
|
|
InternalGameID: row.InternalGameID,
|
|
VendorGameID: row.vendorGameIDInt(),
|
|
GameSessionID: fmt.Sprintf("bs_%s_%d", sanitizeRoomID(req.RoomID), sessionID),
|
|
LaunchCode: launchCode,
|
|
LaunchCodeExpireTime: now.Add(time.Duration(s.cfg.Baishun.LaunchCodeTTLSeconds) * time.Second),
|
|
Language: s.resolveLanguage(ctx, user.UserID),
|
|
GSP: s.resolveGSP(row),
|
|
CurrencyType: 0,
|
|
CurrencyIcon: row.currencyIcon(),
|
|
SceneMode: req.SceneMode,
|
|
GameMode: row.gameMode(),
|
|
PackageVersion: row.packageVersion(),
|
|
ClientIP: clientIP,
|
|
ClientOrigin: defaultIfBlank(req.ClientOrigin, "COMMON"),
|
|
Status: baishunSessionCodeIssued,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
|
|
roomState := model.BaishunRoomState{
|
|
ID: roomStateID,
|
|
SysOrigin: user.SysOrigin,
|
|
RoomID: req.RoomID,
|
|
HostUserID: user.UserID,
|
|
CurrentGameID: row.InternalGameID,
|
|
CurrentGameName: row.name(),
|
|
CurrentGameCover: row.cover(),
|
|
GameSessionID: session.GameSessionID,
|
|
LaunchUserID: &user.UserID,
|
|
State: baishunRoomStatePlaying,
|
|
StartTime: &now,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
vendorGameID := row.vendorGameIDInt()
|
|
roomState.CurrentVendorGameID = &vendorGameID
|
|
|
|
if err := s.repo.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Create(&session).Error; err != nil {
|
|
return err
|
|
}
|
|
return tx.Clauses(clause.OnConflict{
|
|
Columns: []clause.Column{{Name: "sys_origin"}, {Name: "room_id"}},
|
|
DoUpdates: clause.AssignmentColumns([]string{
|
|
"host_user_id", "current_game_id", "current_vendor_game_id", "current_game_name",
|
|
"current_game_cover", "game_session_id", "launch_user_id", "state", "start_time",
|
|
"end_time", "update_time",
|
|
}),
|
|
}).Create(&roomState).Error
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &BaishunLaunchAppResponse{
|
|
GameSessionID: session.GameSessionID,
|
|
VendorType: baishunVendorType,
|
|
GameID: row.InternalGameID,
|
|
VendorGameID: row.vendorGameIDInt(),
|
|
Entry: BaishunLaunchEntry{
|
|
LaunchMode: row.launchMode(),
|
|
EntryURL: row.entryURL(),
|
|
PreviewURL: row.previewURL(),
|
|
DownloadURL: row.downloadURL(),
|
|
PackageVersion: row.packageVersion(),
|
|
Orientation: row.orientation(),
|
|
SafeHeight: row.safeHeight(),
|
|
},
|
|
BridgeConfig: BaishunBridgeConfig{
|
|
AppName: s.cfg.Baishun.AppName,
|
|
AppChannel: defaultIfBlank(s.cfg.Baishun.AppChannel, "skychat"),
|
|
AppID: s.cfg.Baishun.AppID,
|
|
UserID: strconv.FormatInt(user.UserID, 10),
|
|
Code: launchCode,
|
|
RoomID: req.RoomID,
|
|
GameMode: strconv.Itoa(row.gameMode()),
|
|
Language: session.Language,
|
|
GSP: session.GSP,
|
|
GameConfig: BaishunGameConfig{
|
|
SceneMode: req.SceneMode,
|
|
CurrencyIcon: session.CurrencyIcon,
|
|
},
|
|
},
|
|
RoomState: BaishunRoomStateResponse{
|
|
RoomID: req.RoomID,
|
|
State: baishunRoomStatePlaying,
|
|
GameSessionID: session.GameSessionID,
|
|
CurrentGameID: row.InternalGameID,
|
|
CurrentVendorGameID: row.vendorGameIDInt(),
|
|
CurrentGameName: row.name(),
|
|
CurrentGameCover: row.cover(),
|
|
HostUserID: user.UserID,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// CloseGame 关闭房间内当前游戏会话并回写房间状态。
|
|
func (s *BaishunService) CloseGame(ctx context.Context, user AuthUser, req BaishunCloseAppRequest) (*BaishunRoomStateResponse, error) {
|
|
if strings.TrimSpace(req.RoomID) == "" {
|
|
return nil, NewAppError(http.StatusBadRequest, "missing_room_id", "roomId is required")
|
|
}
|
|
now := time.Now()
|
|
if strings.TrimSpace(req.GameSessionID) != "" {
|
|
if err := s.repo.DB.WithContext(ctx).
|
|
Model(&model.BaishunLaunchSession{}).
|
|
Where("sys_origin = ? AND room_id = ? AND game_session_id = ?", user.SysOrigin, req.RoomID, req.GameSessionID).
|
|
Updates(map[string]any{
|
|
"status": baishunSessionClosed,
|
|
"closed_reason": defaultIfBlank(req.Reason, "user_exit"),
|
|
"update_time": now,
|
|
}).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if err := s.repo.DB.WithContext(ctx).
|
|
Model(&model.BaishunRoomState{}).
|
|
Where("sys_origin = ? AND room_id = ?", user.SysOrigin, req.RoomID).
|
|
Updates(map[string]any{
|
|
"state": baishunRoomStateIdle,
|
|
"end_time": now,
|
|
"game_session_id": "",
|
|
"current_game_id": "",
|
|
"current_vendor_game_id": nil,
|
|
"current_game_name": "",
|
|
"current_game_cover": "",
|
|
"update_time": now,
|
|
}).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &BaishunRoomStateResponse{
|
|
RoomID: req.RoomID,
|
|
State: baishunRoomStateIdle,
|
|
}, nil
|
|
}
|
|
|
|
// RefreshRoomState 根据最新会话状态刷新房间当前挂载的游戏信息。
|
|
func (s *BaishunService) RefreshRoomState(ctx context.Context, sysOrigin, roomID string) (*BaishunRoomStateResponse, error) {
|
|
var state model.BaishunRoomState
|
|
if err := s.repo.DB.WithContext(ctx).
|
|
Where("sys_origin = ? AND room_id = ?", sysOrigin, roomID).
|
|
First(&state).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
return &BaishunRoomStateResponse{RoomID: roomID, State: baishunRoomStateIdle}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
if strings.TrimSpace(state.GameSessionID) == "" {
|
|
state.State = baishunRoomStateIdle
|
|
_ = s.repo.DB.WithContext(ctx).Model(&model.BaishunRoomState{}).
|
|
Where("id = ?", state.ID).
|
|
Update("state", baishunRoomStateIdle).Error
|
|
}
|
|
resp := &BaishunRoomStateResponse{
|
|
RoomID: roomID,
|
|
State: defaultIfBlank(state.State, baishunRoomStateIdle),
|
|
GameSessionID: state.GameSessionID,
|
|
CurrentGameID: state.CurrentGameID,
|
|
CurrentGameName: state.CurrentGameName,
|
|
CurrentGameCover: state.CurrentGameCover,
|
|
HostUserID: state.HostUserID,
|
|
}
|
|
if state.CurrentVendorGameID != nil {
|
|
resp.CurrentVendorGameID = *state.CurrentVendorGameID
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// readUserInfo 读取用户资料,用于回调和启动配置。
|
|
func (s *BaishunService) readUserInfo(ctx context.Context, userID int64) integration.UserProfile {
|
|
profile, err := s.java.GetUserProfile(ctx, userID)
|
|
if err != nil {
|
|
return integration.UserProfile{ID: integration.Int64Value(userID)}
|
|
}
|
|
return profile
|
|
}
|
|
|
|
// readUserBalance 读取用户金币余额。
|
|
func (s *BaishunService) readUserBalance(ctx context.Context, userID int64) int64 {
|
|
balanceMap, err := s.java.MapGoldBalance(ctx, []int64{userID})
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return balanceMap[userID]
|
|
}
|
|
|
|
// resolveLanguage 解析用户语言,缺省回退英文。
|
|
func (s *BaishunService) resolveLanguage(ctx context.Context, userID int64) string {
|
|
language, err := s.java.GetUserLanguage(ctx, userID)
|
|
if err != nil || strings.TrimSpace(language) == "" {
|
|
return "0"
|
|
}
|
|
return language
|
|
}
|
|
|
|
// findSessionByToken 按 ss_token 和用户查询会话。
|
|
func (s *BaishunService) findSessionByToken(ctx context.Context, ssToken, userID string) (model.BaishunLaunchSession, bool) {
|
|
var session model.BaishunLaunchSession
|
|
err := s.repo.DB.WithContext(ctx).
|
|
Where("ss_token = ?", strings.TrimSpace(ssToken)).
|
|
First(&session).Error
|
|
if err != nil || time.Now().After(derefTime(session.SSTokenExpireTime)) {
|
|
return model.BaishunLaunchSession{}, false
|
|
}
|
|
if strings.TrimSpace(userID) != "" && strconv.FormatInt(session.UserID, 10) != strings.TrimSpace(userID) {
|
|
return model.BaishunLaunchSession{}, false
|
|
}
|
|
return session, true
|
|
}
|