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 }