服务器运行时示例 #

服务器到服务器 #

与客户端调用不同,服务器到服务器的调用从不会有用户ID,因此,对于任何不希望从客户端访问的函数,如果在上下文中找到用户ID,则返回一个错误。

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func ServerRPC(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 != "" {
		logger.Error("rpc was called by a user")
		return "", runtime.NewError("rpc is only callable via server to server", 7)
	}

	// Valid server to server RPC call, continue executing the RPC...
	return "<JsonResponse>", nil
}
Server
1
2
3
4
5
6
7
8
9
const serverRpc : nkruntime.RpcFunction = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string) : string | void {
  if (ctx.userId != "") {
    logger.error("rpc was called by a user");
    return null;
  }
  
  // Valid server to server RPC call, continue executing the RPC...
  return "<JsonResponse>";
}
Server
1
2
3
4
5
6
7
8
9
local server_rpc = function(context, payload)
    if context.user_id and not context.user_id == "" then
        nk.logger_error("rpc was called by a user")
        return nil
    end

    -- Valid server to server RPC call, continue executing the RPC...
    return "<JsonResponse>"
end

此外,它还有助于创建可供Web服务使用的HTTPREST处理程序,并简化与定制服务器环境的集成。

这可以通过使用RPC挂钩和运行时 HTTP 密钥在服务器上进行身份验证来实现。

Server
1
2
3
4
5
6
7
8
9
local nk = require("nakama")

local function http_handler(context, payload)
  local message = nk.json_decode(payload)
  nk.logger_info(string.format("Message: %q", message))
  return nk.json_encode({["context"] = context})
end

nk.register_rpc(http_handler, "http_handler_path")
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func HttpHandler(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
		var message interface{}
		if err := json.Unmarshal([]byte(payload), &message); err != nil {
				return "", err
		}

		logger.Info("Message: %v", message)

		response, err := json.Marshal(map[string]interface{}{"message": message})
		if err != nil {
				return "", err
		}

		return string(response), nil
}

// Register as an RPC function, this call should be in InitModule.
if err := initializer.RegisterRpc("http_handler_path", HttpHandler); err != nil {
  logger.Error("Unable to register: %v", err)
  return err
}
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
let customFuncRpc: nkruntime.RpcFunction =
function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string) {
	logger.info('payload: %q', payload);

	if (ctx.userId) {
		// Reject non server-to-server call
		throw Error('Cannot invoke this function from user session');
	}

	let message = JSON.parse(payload);
	logger.info('Message: %q', message);

	return JSON.stringify({ message: message });
}

// Register as an after hook for the appropriate feature, this call should be in InitModule.
initializer.registerRpc("custom_rpc_func_id", customFuncRpc);

注册的RPC函数可以用您选择的任何HTTP客户端调用。例如,您可以使用cURL在服务器上执行函数,如下所示。

1
2
3
4
curl "http://127.0.0.1:7350/v2/rpc/http_handler_path?http_key=defaulthttpkey" \
	-d '"{\"some\": \"data\"}"' \
	-H 'Content-Type: application/json' \
	-H 'Accept: application/json'

请注意,JSON有效负载被转义并包装在字符串中。这是设计的结果,因为在设计RPC API时,gRPC没有在Protobuf类型和JSON对象之间进行映射的类型。之后gRPC中增加了对SON的支持,但我们一直保持这种方式,以免违反API契约并确保兼容性。

unwrap查询参数受支持,使您能使用有负载中的原始JSON数据调用RPC函数:

1
2
3
4
curl "http://127.0.0.1:7350/v2/rpc/http_handler_path?http_key=defaulthttpkey&unwrap" \
	-d '{"some": "data"}' \
	-H 'Content-Type: application/json' \
	-H 'Accept: application/json'

初始化用户 #

使用register挂钩在用户完成注册后写入新用户的记录。

"register_after"钩子可以与一种"authenticaterequest_*“消息类型一起使用,指示服务器在处理这个消息后运行函数。需要注意的是,服务器不区分注册和登录消息,因此我们使用有条件写入来存储记录。

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
local function initialize_user(context, payload)
  if payload.created then
    -- Only run this logic if the account that has authenticated is new.
    local changeset = {
      coins = 10,   -- Add 10 coins to the user's wallet.
      gems = 5      -- Add 5 gems to the user's wallet.
      artifacts = 0 -- No artifacts to start with.
    }
    local metadata = {}
    nk.wallet_update(context.user_id, changeset, metadata, true)
  end
end

-- change to whatever message name matches your authentication type.
nk.register_req_after(initialize_user, "AuthenticateDevice")
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func InitializeUser(ctx context.Context, logger Logger, db *sql.DB, nk NakamaModule, out *api.Session, in *api.AuthenticateDeviceRequest) error {
  if out.Created {
    // Only run this logic if the account that has authenticated is new.
    userID, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
    if !ok {
      return "", errors.New("Invalid context")
    }
    changeset := map[string]interface{}{
      "coins": 10,    // Add 10 coins to the user's wallet.
      "gems":  5,     // Add 5 gems to the user's wallet.
      "artifacts": 0, // No artifacts to start with.
    }
    var metadata map[string]interface{}
    if err := nk.WalletUpdate(ctx, userID, changeset, metadata, true); err != nil {
      // Handle error.
    }
  }
}

// Register as after hook, this call should be in InitModule.
if err := initializer.RegisterAfterAuthenticateDevice(InitializeUser); err != nil {
  logger.Error("Unable to register: %v", err)
  return err
}
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let initializeUser : nkruntime.AfterHookFunction<nkruntime.Session, nkruntime.AuthenticateDeviceRequest> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, out: nkruntime.Session, data: nkruntime.AuthenticateDeviceRequest) : nkruntime.Session {
  const changeset = {
    "coins": 10, // Add 10 coins to the user's wallet
    "gems": 5, // Add 5 gems to the user's wallet
    "artifacts": 0 // No artifacts to start with
  };
  
  nk.walletUpdate(ctx.userId, changeset, null, true);
  return out;
};

// Register as after hook, this call should be in InitModule.
initializer.registerAfterAuthenticateDevice(initializeUser);

存储 #

写入存储 #

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func AuthoritativeWriteRPC(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
	userID, _ := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)

	data := map[string]interface{}{
		"achievementPoints": 100,
		"unlockedAchievements": []string{"max-level", "defeat-boss-2", "equip-rare-gear"},
	}

	bytes, err := json.Marshal(data)
	if err != nil {
		return "", runtime.NewError("error marshaling data", 13)
	}

	write := &runtime.StorageWrite{
		Collection:      "Unlocks",
		Key:             "Achievements",
		UserID:          userID,
		Value:           string(bytes),
		PermissionRead:  1, // Only the server and owner can read
		PermissionWrite: 0, // Only the server can write
	}

	_, err = nk.StorageWrite(ctx, []*runtime.StorageWrite{write})
	if err != nil {
		return "", runtime.NewError("error saving data", 13)
	}
	
	return "<JsonResponse>", nil
}
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
let authoritativeWriteRpc : nkruntime.RpcFunction = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string) : string | void {
  const data = {
    achievementPoints: 100,
    unlockedAchievements: ['max-level', 'defeat-boss-2', 'equip-rare-gear']
  };

  const write : StorageWriteRequest = {
    collection: 'Unlocks',
    key: 'Achievements',
    userId: ctx.userId,
    value: data,
    permissionRead: 1, // Only the server and owner can read
    permissionWrite: 0 // Only the server can write
  };

  nk.storageWrite([write]);

  return "<JsonResponse>";
};
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
local authoritative_write_rpc = function(context, payload)
    local data = {
        ["achievementPoints"] = 100,
        ["unlockedAchievements"] = { "max-level", "defeat-boss-2", "equip-rare-gear" }
    }

    local write = {
        collection = "Unlocks",
        key = "Achievements",
        user_id = context.user_id,
        value = data,
        permission_read = 1,
        permission_write = 0
    }
    
    nk.storage_write({ write })

    return "<JsonResponse>"
end

Related Pages