// Package modules 实现 Nakama RPC handler 桩。 // 所有 handler 仅完成参数解析、鉴权调用与 TODO 注释,业务逻辑待后续填充。 package modules import ( "context" "crypto/rand" "database/sql" "encoding/hex" "encoding/json" "github.com/heroiclabs/nakama-common/runtime" ) // newTraceID 生成简易追踪 ID。 func newTraceID() string { b := make([]byte, 16) _, _ = rand.Read(b) return hex.EncodeToString(b) } // userIDFromCtx 从 Nakama runtime context 读取用户 ID。 func userIDFromCtx(ctx context.Context) string { if v := ctx.Value(runtime.RUNTIME_CTX_USER_ID); v != nil { if s, ok := v.(string); ok { return s } } return "" } // writeJSON 将响应对象序列化为 JSON 字符串。 func writeJSON(v interface{}) (string, error) { data, err := json.Marshal(v) if err != nil { return "", err } return string(data), nil } // commonResp 构造统一响应外层。 type commonResp struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` TraceID string `json:"trace_id"` } func okResp(data interface{}, traceID string) (string, error) { return writeJSON(commonResp{Code: 0, Message: "success", Data: data, TraceID: traceID}) } func errResp(code int, message string, traceID string) (string, error) { return writeJSON(commonResp{Code: code, Message: message, Data: nil, TraceID: traceID}) } // ----------------------------------------------------------------------------- // Auth // ----------------------------------------------------------------------------- // RegisterAuth 向 Nakama 注册账号相关 RPC。 func RegisterAuth(initializer runtime.Initializer) error { if err := initializer.RegisterRpc("AuthService/Register", authRegister); err != nil { return err } if err := initializer.RegisterRpc("AuthService/Login", authLogin); err != nil { return err } return nil } type registerReq struct { DeviceID string `json:"device_id"` Platform string `json:"platform"` InviteCode string `json:"invite_code"` } type loginReq struct { DeviceID string `json:"device_id"` Platform string `json:"platform"` RefreshToken string `json:"refresh_token"` } type authData struct { PlayerID string `json:"player_id"` NakamaToken string `json:"nakama_token"` ExpiresAt string `json:"expires_at"` IsNew bool `json:"is_new"` } func authRegister(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) { traceID := newTraceID() var req registerReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(1001, "invalid payload", traceID) } // 校验参数 if req.DeviceID == "" { return errResp(1003, "device_id required", traceID) } if req.Platform == "" { return errResp(1004, "platform required", traceID) } // 检查设备是否已注册 var existingPlayerID string err := db.QueryRowContext(ctx, ` SELECT id FROM players WHERE device_id_hash = $1 `, req.DeviceID).Scan(&existingPlayerID) if err == nil { // 设备已注册,返回已有账号 return okResp(authData{ PlayerID: existingPlayerID, IsNew: false, }, traceID) } // 创建新玩家 var playerID string err = db.QueryRowContext(ctx, ` INSERT INTO players (nakama_user_id, platform, device_id_hash, status) VALUES (gen_random_uuid(), $1, $2, 'active') RETURNING id::text `, req.Platform, req.DeviceID).Scan(&playerID) if err != nil { logger.Error("auth register failed: %v", err) return errResp(9002, "internal error", traceID) } logger.Info("AuthService/Register success: player_id=%s", playerID) return okResp(authData{ PlayerID: playerID, IsNew: true, }, traceID) } func authLogin(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) { traceID := newTraceID() var req loginReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(1002, "invalid payload", traceID) } // 校验参数 if req.DeviceID == "" { return errResp(1003, "device_id required", traceID) } // 查询玩家 var playerID string var status string err := db.QueryRowContext(ctx, ` SELECT id, status FROM players WHERE device_id_hash = $1 `, req.DeviceID).Scan(&playerID, &status) if err != nil { return errResp(1005, "player not found", traceID) } if status != "active" { return errResp(1006, "account not active", traceID) } // 更新最后在线时间 _, err = db.ExecContext(ctx, ` UPDATE players SET last_online_at = NOW() WHERE id = $1 `, playerID) if err != nil { logger.Error("auth login update failed: %v", err) } logger.Info("AuthService/Login success: player_id=%s", playerID) return okResp(authData{ PlayerID: playerID, IsNew: false, }, traceID) }