2026-04-20 16:07:22 +08:00

184 lines
5.2 KiB
Go
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}