184 lines
5.2 KiB
Go
Executable File
184 lines
5.2 KiB
Go
Executable File
package handlers
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io/ioutil"
|
||
|
||
"wuziqi-server/config"
|
||
|
||
"github.com/heroiclabs/nakama-common/runtime"
|
||
)
|
||
|
||
func RpcListMatches(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
|
||
limit := 100
|
||
authoritative := true
|
||
label := "" // 我们想列出所有 animal_minesweeper
|
||
minSize := 0
|
||
maxSize := 8
|
||
query := "*" // 默认查询
|
||
|
||
matches, err := nk.MatchList(ctx, limit, authoritative, label, &minSize, &maxSize, query)
|
||
if err != nil {
|
||
logger.Error("Failed to list matches: %v", err)
|
||
return "", err
|
||
}
|
||
|
||
result := make([]map[string]interface{}, 0)
|
||
for _, m := range matches {
|
||
var labelObj MatchLabel
|
||
if err := json.Unmarshal([]byte(m.GetLabel().Value), &labelObj); err != nil {
|
||
// 如果不是 minesweeper 房间,跳过
|
||
continue
|
||
}
|
||
|
||
result = append(result, map[string]interface{}{
|
||
"match_id": m.GetMatchId(),
|
||
"player_count": labelObj.PlayerCount,
|
||
"max_players": labelObj.MaxPlayers,
|
||
"started": labelObj.Started,
|
||
"open": labelObj.Open,
|
||
"game_type": labelObj.GameType,
|
||
})
|
||
}
|
||
|
||
response, err := json.Marshal(result)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
return string(response), nil
|
||
}
|
||
|
||
func RpcFindMyMatch(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
|
||
userID, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
|
||
if !ok {
|
||
return "", runtime.NewError("user not authenticated", 16)
|
||
}
|
||
|
||
readObjects, err := nk.StorageRead(ctx, []*runtime.StorageRead{
|
||
{
|
||
Collection: "game_data",
|
||
Key: "active_match",
|
||
UserID: userID,
|
||
},
|
||
})
|
||
|
||
if err != nil {
|
||
logger.Error("Failed to read storage: %v", err)
|
||
return "", err
|
||
}
|
||
|
||
if len(readObjects) == 0 {
|
||
return "{}", nil
|
||
}
|
||
|
||
return readObjects[0].Value, nil
|
||
}
|
||
|
||
// RpcGetLeaderboard 代理到后端排行榜接口
|
||
func RpcGetLeaderboard(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
|
||
userID, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
|
||
if !ok || userID == "" {
|
||
return "", runtime.NewError("user not authenticated", 16)
|
||
}
|
||
|
||
var req struct {
|
||
GameType string `json:"game_type"`
|
||
Page int `json:"page"`
|
||
PageSize int `json:"page_size"`
|
||
}
|
||
if payload != "" {
|
||
_ = json.Unmarshal([]byte(payload), &req)
|
||
}
|
||
if req.GameType == "" {
|
||
req.GameType = "minesweeper"
|
||
}
|
||
if req.Page <= 0 {
|
||
req.Page = 1
|
||
}
|
||
if req.PageSize <= 0 {
|
||
req.PageSize = 10
|
||
}
|
||
|
||
url := fmt.Sprintf("%s/game/leaderboard?game_type=%s&page=%d&page_size=%d",
|
||
config.BackendBaseURL, req.GameType, req.Page, req.PageSize)
|
||
|
||
resp, err := config.MakeInternalRequest("GET", url, nil)
|
||
if err != nil {
|
||
logger.Error("Failed to get leaderboard: %v", err)
|
||
return "{}", nil
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, _ := ioutil.ReadAll(resp.Body)
|
||
return string(body), nil
|
||
}
|
||
func RpcGetOnlineCount(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
|
||
userID, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
|
||
if !ok || userID == "" {
|
||
return "", runtime.NewError("user not authenticated", 16)
|
||
}
|
||
|
||
// 获取所有活跃比赛中的玩家数
|
||
matches, err := nk.MatchList(ctx, 100, true, "", nil, nil, "*")
|
||
if err != nil {
|
||
logger.Warn("Failed to list matches: %v", err)
|
||
}
|
||
|
||
inGameCount := 0
|
||
for _, m := range matches {
|
||
inGameCount += int(m.GetSize())
|
||
}
|
||
|
||
// 统计在线人数:优先使用大厅频道,备选使用全局 WebSocket 连接数
|
||
onlineCount := int64(0)
|
||
|
||
// 方法1: 通过大厅频道统计(最精准,只统计扫雷游戏的在线玩家)
|
||
// Nakama 频道的内部 Stream 标识:
|
||
// - 对于 Room (Type 1),内部 Stream Mode 是 2
|
||
// - 频道 ID 格式:{stream_mode}...{label}
|
||
// - 例如:2...minesweeper_lobby
|
||
lobbyLabel := "minesweeper_lobby"
|
||
|
||
// StreamUserList 参数:(mode int, subject, subcontext, label string, includeHidden, includeNotHidden bool)
|
||
// Mode 2 = Chat Room Channel
|
||
// subject 和 subcontext 留空,label 是频道标识符
|
||
presences, err := nk.StreamUserList(2, "", "", lobbyLabel, true, true)
|
||
if err != nil {
|
||
logger.Warn("Failed to get lobby channel presences: %v", err)
|
||
} else if len(presences) > 0 {
|
||
onlineCount = int64(len(presences))
|
||
logger.Info("Lobby channel '%s' has %d users (via StreamUserList)", lobbyLabel, onlineCount)
|
||
}
|
||
|
||
// 方法2: 如果频道统计失败或为0,使用全局 WebSocket 连接统计作为备选
|
||
if onlineCount == 0 {
|
||
// StreamCount 参数:(mode int, subject, subcontext, label string)
|
||
// Mode 0 = Status (全局 WebSocket 连接)
|
||
streamCount, err := nk.StreamCount(0, "", "", "")
|
||
if err != nil {
|
||
logger.Warn("Failed to get stream count: %v", err)
|
||
} else if streamCount > 0 {
|
||
onlineCount = int64(streamCount)
|
||
logger.Info("Using global WebSocket connection count: %d", onlineCount)
|
||
}
|
||
}
|
||
|
||
// 兜底:如果所有方式都统计为0,至少算上当前请求的用户
|
||
if onlineCount < 1 {
|
||
onlineCount = 1
|
||
}
|
||
|
||
result := map[string]interface{}{
|
||
"online_count": onlineCount,
|
||
"match_count": len(matches),
|
||
"in_game_count": inGameCount,
|
||
}
|
||
|
||
response, _ := json.Marshal(result)
|
||
return string(response), nil
|
||
}
|