178 行
4.7 KiB
Go
178 行
4.7 KiB
Go
// 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)
|
|
}
|