# 用户账户

**URL:** https://heroiclabs.com/docs/zh/nakama/concepts/user-accounts/
**Summary:** 在服务器中，用户以个人身份的形式存在。每个用户都经过注册，设有个人资料，可供其他玩家查找和添加好友或加入群组或聊天。用户可以拥有记录，与其他用户共享公开信息，并通过各种社交供应商进行身份验证。

---


# 用户账户

在服务器中，用户以个人身份的形式存在。每个用户都经过注册，设有个人资料，可供其他玩家查找和添加[好友](../friends/)或加入[群组](../groups/)或[聊天](../chat/)。

用户可以拥有[记录](../storage/permissions/)，将公开信息共享给其他用户，并通过各种社交供应商进行身份验证。

{{< note "important" >}}
系统所有者身份使用带有空UUID (`00000000-0000-0000-0000-000000000000`).
{{< / note >}}的用户账户表示

## 获取账户

用户有会话时，您可以检索用户账户。个人资料包含各种信息，其中包括各种有“链接”关系的社交供应商。

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/account" \
  -H 'authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
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);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
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);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NAccount& account)
{
    std::cout << "User's wallet: " << account.wallet.c_str() << std::endl;
};

client->getAccount(session, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
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());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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)
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/account
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
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))
```
{{< / code >}}

钱包、设备ID和自定义ID是私密信息，但部分资料对其他用户是可见的。

{{< table name="nakama.concepts.user-accounts.public-fields" >}}

{{< table name="nakama.concepts.user-accounts.private-fields" >}}

### 用户元数据

您可以在`user.metadata`中为用户存储其他字段，这可以帮助您与其他用户共享您想要公开的数据。我们建议使用用户元数据来存储显示给其他用户的常见字段。例如，必要时可以使用用户元数据让用户显示个人详情，或者其角色名称、等级和游戏统计信息。

您可以将所有其他信息存储为拥有[公开阅读权限](../storage/collections/)，以便于其他用户查找。

每位用户的元数据大小限制在16KB。仅可以通过[脚本运行](../../server-framework/)设置，类似于`wallet`。

以下示例展示了使用用户元数据存储VIP状态，然后将VIP状态用作参加锦标赛前的钩子，这个钩子仅允许VIP成员加入锦标赛。

{{< code type="server" >}}
```typescript
// 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);
```
{{< / code >}}

{{< code type="server" >}}
```go
// 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
}
```
{{< / code >}}

{{< code type="server" >}}
```lua
-- 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")
```
{{< / code >}}

### 虚拟钱包

Nakama有虚拟钱包和交易分类账的概念。Nakama允许开发人员创建、更新和列出对用户钱包作出的变更。这个操作有事务担保，并且仅可通过[脚本运行](../../server-framework/)来实现。

通过服务器端代码，可以更新用户的钱包。

{{< code type="server" >}}
```lua
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)
```
{{< / code >}}

{{< code type="server" >}}
```go
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.")
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
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
}
```
{{< / code >}}

钱包属于用户隐私，对其他用户不可见。您可以通过[获取账户](../user-accounts/#fetch-account)操作获取用户的钱包信息。

### 在线指示器

Nakama可通过两种方式汇报用户在线指示器：

1. [获取用户](../user-accounts/#fetch-users)信息。这将为您提供用户在线指示器的快速快照视图，但无法可靠地检测用户显示的在线状态。
2. 发布和订阅用户[显示的在线状态](../status/)更新。用户的在线状态发生变化时，将会向您更新（同时还有自定义消息）。

## 获取用户

您可以通过用户的ID或手柄获取一个或多个用户。这有助于向其他用户显示公开的个人资料。

{{< code type="client" >}}
```bash
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>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
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);
});
```
{{< / code >}}

{{< code type="client" >}}
```csharp
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);
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
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);
```
{{< / code >}}

{{< code type="client" >}}
```java
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());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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])
```
{{< / code >}}

{{< code type="client" >}}
```shell
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>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
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
```
{{< / code >}}

您也可以在服务器端代码中获取一个或多个用户。

{{< code type="server" >}}
```lua
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
```
{{< / code >}}

{{< code type="server" >}}
```go
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)
    }
}
```
{{< / code >}}

## 更新账户

用户注册时，大部分个人资料采用默认值设置。用户可以更新自己的个人资料以更改字段，但无法修改其他用户的个人资料。

{{< code type="client" >}}
```bash
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"
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
await client.updateAccount(session, {
  display_name: "My new name",
  avatar_url: "http://graph.facebook.com/avatar_url",
  location: "San Francisco"
});
```
{{< / code >}}

{{< code type="client" >}}
```csharp
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);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
client->updateAccount(session, opt::nullopt, "My new name", "http://graph.facebook.com/avatar_url", opt::nullopt, "San Francisco");
```
{{< / code >}}

{{< code type="client" >}}
```java
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);
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
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")
```
{{< / code >}}

{{< code type="client" >}}
```shell
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"
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
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")
```
{{< / code >}}

通过服务器端代码，可以更新任意用户的个人资料。

{{< code type="server" >}}
```lua
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
```
{{< / code >}}

{{< code type="server" >}}
```go
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())
}
```
{{< / code >}}
