package handlers import ( "context" "database/sql" "encoding/json" "time" "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, }) } 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 } // RpcGetOnlineCount 返回当前在线玩家数量(基于心跳) 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) } // 1. 更新当前用户的心跳时间戳 now := time.Now().Unix() heartbeatData, _ := json.Marshal(map[string]int64{"ts": now}) _, err := nk.StorageWrite(ctx, []*runtime.StorageWrite{ { Collection: "game_lobby", Key: "heartbeat", UserID: userID, Value: string(heartbeatData), PermissionRead: 0, // 不可读 PermissionWrite: 0, // 仅服务器可写 }, }) if err != nil { logger.Warn("Failed to write heartbeat: %v", err) } // 2. 读取所有用户的心跳记录 cursor := "" onlineCount := 0 expireThreshold := now - 60 // 60秒内有心跳的算在线 for { // StorageList(ctx, callerID, collection, userID, limit, cursor) objects, nextCursor, err := nk.StorageList(ctx, "", "game_lobby", "", 100, cursor) if err != nil { logger.Error("Failed to list heartbeats: %v", err) break } for _, obj := range objects { var data map[string]int64 if err := json.Unmarshal([]byte(obj.Value), &data); err != nil { continue } if ts, ok := data["ts"]; ok && ts >= expireThreshold { onlineCount++ } } if nextCursor == "" { break } cursor = nextCursor } // 3. 获取比赛中的玩家数 matches, _ := nk.MatchList(ctx, 100, true, "", nil, nil, "*") inGameCount := 0 for _, m := range matches { inGameCount += int(m.GetSize()) } result := map[string]interface{}{ "online_count": onlineCount, "match_count": len(matches), "in_game_count": inGameCount, } response, _ := json.Marshal(result) return string(response), nil }