lawless/server/modules/auth.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)
}