사용자 계정 #

사용자란 서버 내의 ID입니다. 모든 사용자는 등록되어 있고 프로필이 있어 다른 사용자가 찾고 친구가 되거나 그룹에 가입하고 채팅할 수 있습니다.

사용자는 기록을 보유하고 다른 사용자와 공개 정보를 공유하고 다양한 소셜 제공자를 통해 인증할 수 있습니다.

시스템 소유자 ID는 UUID가 nil인 사용자 계정으로 표시됩니다(00000000-0000-0000-0000-000000000000).

계정 가져오기 #

사용자에게 세션이 있는 경우 여러분은 해당 계정을 검색할 수 있습니다. 프로필에는 다수의 “연결된” 소셜 제공자가 포함된 여러가지 정보가 있습니다.

Client
1
2
curl -X GET "http://127.0.0.1:7350/v2/account" \
  -H 'authorization: Bearer <session token>'
Client
1
2
3
4
const account = await client.getAccount(session);
const user = account.user;
console.info("User id '%o' and username '%o'.", user.id, user.username);
console.info("User's wallet:", account.wallet);
Client
1
2
3
4
var account = await client.GetAccountAsync(session);
var user = account.User;
System.Console.WriteLine("User id '{0}' username '{1}'", user.Id, user.Username);
System.Console.WriteLine("User wallet: '{0}'", account.Wallet);
Client
1
2
3
4
5
6
auto successCallback = [](const NAccount& account)
{
    std::cout << "User's wallet: " << account.wallet.c_str() << std::endl;
};

client->getAccount(session, successCallback);
Client
1
2
3
4
Account account = client.getAccount(session);
User user = account.getUser();
System.out.format("User id %s username %s", user.getId(), user.getUsername());
System.out.format("User wallet %s", account.getWallet());
Client
1
2
3
4
5
6
7
8
9
var account : NakamaAPI.ApiAccount = yield(client.get_account_async(session), "completed")

if account.is_exception():
    print("An error occurred: %s" % account)
    return

var user = account.user
print("User id '%s' and username '%s'." % [user.id, user.username])
print("User's wallet: %s." % account.wallet)
Client
1
2
3
4
5
GET /v2/account
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
1
2
3
4
5
6
7
8
9
local result = client.get_account()

if result.error then
  print(result.message)
  return
end

local user = result.user
print(("User id '%s' and username '%s'."):format(user.id, user.username))

지갑, 장치 ID, 사용자 지정 ID와 같은 일부 정보는 비공개지만 프로필의 일부는 다른 사용자에게 표시됩니다.

Public FieldDescription
user.id사용자의 고유 식별자.
user.username사용자의 고유 애칭.
user.display_name사용자의 표시 이름(기본적으로 비어 있음).
user.avatar_url사용자의 프로필 사진이 포함된 URL(기본적으로 비어 있음).
user.lang사용자의 기본 설정 언어(기본값은 “en”).
user.location사용자의 위치(기본적으로 비어 있음).
user.timezone사용자의 시간대(기본적으로 비어 있음).
user.metadata사용자를 위한 사용자 지정 정보용 슬롯 - 클라이언트에서만 읽을 수 있습니다.
user.edge_count이 사용자의 친구 수.
user.facebook_id이 사용자와 연결된 Facebook 식별자.
user.google_id이 사용자와 연결된 Google 식별자.
user.gamecenter_id이 사용자와 연결된 GameCenter 식별자.
user.steam_id이 사용자와 연결된 Steam 식별자.
user.create_time사용자가 생성된 시간의 타임스탬프.
user.update_time사용자가 마지막으로 업데이트된 시간의 타임스탬프.
user.online사용자가 현재 온라인 상태인지 여부를 나타내는 부울.
Private FieldDescription
email이 사용자와 연결된 이메일 주소.
devices이 사용자와 연결된 장치 ID의 목록.
custom_id이 사용자와 연결된 사용자 지정 식별자.
wallet사용자 지갑 - 클라이언트에서만 읽을 수 있습니다.
verify_time사용자가 확인된 시점의 타임스탬프(현재 Facebook에서만 사용됨).

사용자 메타데이터 #

다른 사용자에게 데이터를 공개하여 공유하는데 유용한 user.metadata 내의 사용자를 위해 추가 필드를 저장할 수 있습니다. 다른 사용자가 볼 필요가 있는 매우 일반적인 필드를 저장하려면 사용자 메타데이터를 사용하는 것이 좋습니다. 예를 들어 메타데이터를 사용하여 사용자가 원하는 경우 약력 세부 정보를 표시하거나 해당 캐릭터 이름, 수준 및 게임 통계를 표시할 수 있습니다.

다른 모든 정보의 경우 다른 사용자가 찾을 수 있도록 공개 읽기 권한으로 레코드를 저장할 수 있습니다.

메타데이터는 사용자당 16KB로 제한됩니다. wallet과(와) 유사한 스크립트 런타임을 통해서만 설정할 수 있습니다.

다음 예에서는 사용자 메타데이터를 사용하여 VIP 상태를 저장한 다음 VIP 회원만 가입할 수 있는 사전 가입 토너먼트 후크의 일부로 사용하는 방법을 보여줍니다.

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
// Assuming a user metadata structure as follows
const metadata = {
  vip: true
};

// Add a before hook to only allow VIP users to join the vip_only tournament
let BeforeJoinTournament: nkruntime.BeforeHookFunction<JoinTournamentRequest> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, data: nkruntime.JoinTournamentRequest): nkruntime.JoinTournamentRequest | void {
  const account = nk.accountGetId(ctx.userId)

  // Only do the following checks if the tournament id is `vip_only`
  if (data.tournamentId != "vip_only") {
    return data;
  }

  // Only continue with the Join Tournament if the actioning user is a vip
  if (account.user.metadata["vip"]) {
    return data;
  }

  logger.warn("you must be a vip to join this tournament")
  return null;
};

// Register inside InitModule
initializer.registerBeforeJoinTournament(BeforeJoinTournament);
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Assuming a user metadata structure as follows
type UserMetadata struct {
	Vip bool `json:"vip"`
}

metadata := &UserMetadata {
  Vip: true,
}

// Add a before hook to only allow VIP users to join the vip_only tournament
if  err := initializer.RegisterBeforeJoinTournament(func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.JoinTournamentRequest) (*api.JoinTournamentRequest, error) {
  userId, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
  if !ok {
    logger.Error("invalid user")
    return nil, runtime.NewError("invalid user", 13)
  }

  // Get the user's metadata
  account, err := nk.AccountGetId(ctx, userId)
  if err != nil {
    logger.Error("error getting user account")
    return nil, runtime.NewError("error getting user account", 13)
  }

  // Only do the following checks if the tournament id is `vip_only`
  if in.TournamentId != "vip_only" {
    return in, nil
  }

  // Only continue with the Join Tournament if the actioning user is a vip
  var metadata UserMetadata
  if err := json.Unmarshal([]byte(account.User.GetMetadata()), &metadata); err != nil {
    logger.Error("error deserializing metadata")
    return nil, runtime.NewError("error deserializing metadata", 13)
  }

  if metadata.Vip {
    return in, nil
  }

  return nil, runtime.NewError("you must be a vip user to join this tournament", 7)
}); err != nil {
  logger.Error("unable to register before join tournament hook: %v", err)
  return err
}
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
-- Assuming a user metadata structure as follows
local metadata = {
    ["vip"] = true
}

-- Add a before hook to only allow VIP users to join the vip_only tournament
local function before_join_tournament(context, payload)
    local account = nk.account_get_id(context.user_id)

    -- Only do the following checks if the tournament id is `vip_only`
    if payload.tournament_id ~= "vip_only" then
        return payload
    end

    -- Only continue with the Join Tournament request if the actioning user is a vip
    if account.user.metadata["vip"] then
        return payload
    end

    nk.logger_error("you must be a vip user to join this tournament")
    return nil
end

nk.register_req_before(before_join_tournament, "JoinTournament")

가상 지갑 #

Nakama에는 가상 지갑과 거래 원장이라는 개념이 사용되고 있습니다. Nakama를 사용하면 개발자는 사용자 지갑에 대한 변경 사항을 생성, 업데이트 및 나열할 수 있습니다. 이 작업에서 거래는 보장되며 스크립트 런타임에서만 수행할 수 있습니다.

서버 측 코드를 사용하면 사용자의 지갑을 업데이트할 수 있습니다.

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
local nk = require("nakama")
local user_id = "8f4d52c7-bf28-4fcf-8af2-1d4fcf685592"

local changeset = {
  coins = 10, -- Add 10 coins to the user's wallet.
  gems = -5   -- Remove 5 gems from the user's wallet.
}

local metadata = {
  game_result = "won"
}

local updated, previous = nk.wallet_update(user_id, changeset, metadata, true)
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
userID := "8f4d52c7-bf28-4fcf-8af2-1d4fcf685592"
changeset := map[string]interface{}{
    "coins": 10, // Add 10 coins to the user's wallet.
    "gems":  -5, // Remove 5 gems from the user's wallet.
}
metadata := map[string]interface{}{
    "game_result": "won",
}
updated, previous, err := nk.WalletUpdate(ctx, userID, changeset, metadata, true)
if err != nil {
    logger.WithField("err", err).Error("Wallet update error.")
}
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
let user_id = '8f4d52c7-bf28-4fcf-8af2-1d4fcf685592';

let changeset = {
  coins: 10, // Add 10 coins to the user's wallet.
  gems: -5,   // Remove 5 gems from the user's wallet.
};

let metadata = {
  gameResult: 'won'
};

let result: nkruntime.WalletUpdateResult;

try {
    result = nk.walletUpdate(user_id, changeset, metadata, true);
} catch (error) {
    // Handle error
}

지갑은 사용자에게만 표시되며 다른 사용자에게는 비공개입니다. 계정 가져오기 작업을 통해 사용자의 지갑 정보를 가져올 수 있습니다.

온라인 표시기 #

Nakama는 두 가지 방법으로 사용자 온라인 표시기를 보고할 수 있습니다:

  1. 사용자 정보를 가져옵니다. 이렇게 하면 사용자의 온라인 표시기가 빠른 스냅샷 보기로 제공되지만 이는 사용자 현재 상태를 감지하는 신뢰할 수 있는 방법은 아닙니다.
  2. 사용자 현재 상태 업데이트를 게시하고 구독합니다. 이에 의해 사용자의 온라인 상태가 바뀌면 업데이트가 제공됩니다(사용자 지정 메시지와 함께).

사용자 가져오기 #

ID 또는 핸들로 한 명 이상의 사용자를 가져올 수 있습니다. 이 방법으로 다른 사용자와 공개 프로필을 쉽게 표시할 수 있습니다.

Client
1
2
curl -X GET "http://127.0.0.1:7350/v2/user?ids=userid1&ids=userid2&usernames=username1&usernames=username2&facebook_ids=facebookid1" \
  -H 'authorization: Bearer <session token>'
Client
1
2
3
4
5
const users = await client.getUsers(session, ["user_id1"], ["username1"], ["facebookid1"]);

users.foreach(user => {
  console.info("User id '%o' and username '%o'.", user.id, user.username);
});
Client
1
2
3
4
5
6
7
8
9
var ids = new[] {"userid1", "userid2"};
var usernames = new[] {"username1", "username2"};
var facebookIds = new[] {"facebookid1"};
var result = await client.GetUsersAsync(session, ids, usernames, facebookIds);

foreach (var u in result.Users)
{
    System.Console.WriteLine("User id '{0}' username '{1}'", u.Id, u.Username);
}
Client
1
2
3
4
5
6
7
8
auto successCallback = [](const NUsers& users)
{
    for (auto& user : users.users)
    {
        std::cout << "User id '" << user.id << "' username " << user.username << std::endl;
    }
};
client->getUsers(session, { "user_id1" }, { "username1" }, { "facebookid1" }, successCallback);
Client
1
2
3
4
5
6
7
8
List<String> ids = Arrays.asList("userid1", "userid2");
List<String> usernames = Arrays.asList("username1", "username1");
String[] facebookIds = new String[] {"facebookid1"};
Users users = client.getUsers(session, ids, usernames, facebookIds).get();

for (User user : users.getUsersList()) {
  System.out.format("User id %s username %s", user.getId(), user.getUsername());
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var ids = ["userid1", "userid2"]
var usernames = ["username1", "username2"]
var facebook_ids = ["facebookid1"]
var result : NakamaAPI.ApiUsers = yield(client.get_users_async(session, ids, usernames, facebook_ids), "completed")

if result.is_exception():
    print("An error occurred: %s" % result)
    return

for u in result.users:
    print("User id '%s' username '%s'" % [u.id, u.username])
Client
1
2
3
4
5
GET /v2/user?ids=userid1&ids=userid2&usernames=username1&usernames=username2&facebook_ids=facebookid1
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
local ids = { "userid1", "userid2" }
local usernames = { "username1", "username2" }
local facebook_ids = { "facebookid1" }
local result = client.get_users(ids, usernames, facebook_ids)

if result.error then
  print(result.message)
  return
end

local users = result.users

for _,user in ipairs(users) do
  print(("User id '%s' and username '%s'."):format(user.id, user.username))
end

서버 측 코드에서 한 명 이상의 사용자를 가져올 수도 있습니다.

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

local user_ids = {
  "3ea5608a-43c3-11e7-90f9-7b9397165f34",
  "447524be-43c3-11e7-af09-3f7172f05936"
}

local users = nk.users_get_id(user_ids)

for _, u in ipairs(users) do
  local message = ("username: %q, displayname: %q"):format(u.username, u.display_name)
  nk.logger_info(message)
end
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
if users, err := nk.UsersGetId(ctx, []string{
    "3ea5608a-43c3-11e7-90f9-7b9397165f34",
    "447524be-43c3-11e7-af09-3f7172f05936",
}); err != nil {
    // Handle error.
} else {
    for _, u := range users {
      logger.Info("username: %s, displayname: %s", u.Username, u.DisplayName)
    }
}

계정 업데이트 #

사용자가 등록되면 대부분의 프로필이 기본값으로 설정됩니다. 사용자는 필드를 바꾸기 위해 자신의 프로필을 업데이트할 수 있지만 다른 사용자의 프로필은 변경할 수 없습니다.

Client
1
2
3
4
5
6
7
curl -X PUT "http://127.0.0.1:7350/v2/account" \
  -H 'authorization: Bearer <session token>' \
  --data '{
    "display_name": "My new name",
    "avatar_url": "http://graph.facebook.com/avatar_url",
    "location": "San Francisco"
  }'
Client
1
2
3
4
5
await client.updateAccount(session, {
  display_name: "My new name",
  avatar_url: "http://graph.facebook.com/avatar_url",
  location: "San Francisco"
});
Client
1
2
3
4
const string displayName = "My new name";
const string avatarUrl = "http://graph.facebook.com/avatar_url";
const string location = "San Francisco";
await client.UpdateAccountAsync(session, null, displayName, avatarUrl, null, location);
Client
1
client->updateAccount(session, opt::nullopt, "My new name", "http://graph.facebook.com/avatar_url", opt::nullopt, "San Francisco");
Client
1
2
3
4
String displayName = "My new name";
String avatarUrl = "http://graph.facebook.com/avatar_url";
String location = "San Francisco";
client.updateAccount(session, null, displayName, avatarUrl, null, location);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var display_name = "My new name";
var avatar_url = "http://graph.facebook.com/avatar_url";
var location = "San Francisco";
var update : NakamaAsyncResult = yield(client.update_account_async(session, null, display_name, avatar_url, null, location), "completed")

if update.is_exception():
    print("An error occurred: %s" % update)
    return

print("Account updated")
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
PUT /v2/account HTTP/1.1
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "display_name": "My new name",
  "avatar_url": "http://graph.facebook.com/avatar_url",
  "location": "San Francisco"
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
local display_name = "Björn"
local avatar_url = "http://graph.facebook.com/avatar_url"
local lang_tag = ""
local timezone = ""
local username = ""
local location = "Stockholm"

local result = client.update_account(client, avatar_url, display_name, lang_tag, location, timezone, username)

if result.error then
  print(result.message)
  return
end

print("Account updated")

서버 측 코드를 사용하면 모든 사용자의 프로필을 업데이트할 수 있습니다.

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
local nk = require("nakama")

local user_id = "4ec4f126-3f9d-11e7-84ef-b7c182b36521" -- some user's id.
local metadata = {}
local username = "my-new-username"
local display_name = "My new Name"
local timezone = nil
local location = "San Francisco"
local lang_tag = nil
local avatar_url = "http://graph.facebook.com/avatar_url"

local status, err = pcall(nk.account_update_id, user_id, metadata, username, display_name, timezone, location, lang_tag, avatar_url)

if (not status) then
  nk.logger_info(("Account update error: %q"):format(err))
end
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
userID := "4ec4f126-3f9d-11e7-84ef-b7c182b36521" // some user's id.
username := "my-new-username" // must be unique
metadata := make(map[string]interface{})
displayName := "My new name"
timezone := ""
location := "San Francisco"
langTag := ""
avatarUrl := "http://graph.facebook.com/avatar_url"

if err := nk.AccountUpdateId(ctx, userID, username, metadata, displayName, timezone, location, langTag, avatarUrl); err != nil {
    // Handle error.
    logger.Error("Account update error: %s", err.Error())
}

Related Pages