# 通知

**URL:** https://heroiclabs.com/docs/zh/nakama/concepts/notifications/
**Summary:** 借助应用程序內通知，可以方便地将消息广播给一个或多个用户。这些通知非常适合发送公告、警告或游戏内奖励及礼品通知可将通知存储到其被读取或推送时为止，所以将只让一个连接的用户看到。

---


# 应用程序内通知

借助应用程序內通知，可以方便地将消息广播给一个或多个用户。这些通知非常适合发送公告、警告或游戏内奖励及礼品通知。

通知可以存储到下次打开应用程序时被读取时为止，也可以推送通知，会只让一个连接的用户看到。您还可以使用通知在游戏中触发自定义操作，并改变客户端行为。

这些通知是在应用程序内查看的，因此它们非常适合推送在应用程序外查看的通知。

## 发送通知

您可以用服务器端代码向一个或多个用户发送通知。可将通知发送给游戏中的每个人 – 并非只有好友才能交换消息。服务器也隐式发送各种特定事件的许多通知。

每条通知有一个代码，这用于对其进行分类。您为通知选择的代码必须从 `0` 开始并向上增加。请参阅[下方](#notification-codes)保留消息代码。

通知有一个将被编码为 JSON 的内容对象。

发送后，可将通知标记为持久。只有当前连接到服务器的客户端（即在线用户）才会接收到非持久消息。如需要确保通知在被读取前永不丢失，在发送时应将其标记为持久的。

{{< code type="server" >}}
```lua
local nk = require("nakama")

local user_id = "user id to send to"
local sender_id = nil -- "nil" for server sent.
local content = {
  item_id = "192308394345345",
  item_icon = "storm_bringer_sword.png"
}
local subject = "You earned a secret item!"
local code = 1
local persistent = true

nk.notification_send(user_id, subject, content, code, sender_id, persistent)
```
{{< / code >}}

{{< code type="server" >}}
```go
subject := "You earned a secret item!"
content := map[string]interface{}{
    "item_id": "192308394345345",
    "item_icon": "storm_bringer_sword.png"
}
userID := "user id to send to"
senderID := "" // Empty string for server sent.
code := 1
persistent := true

nk.NotificationSend(ctx, userID, subject, content, code, senderID, persistent)
```
{{< / code >}}

{{< code type="server" >}}
```typescript
let receiverId = '4c2ae592-b2a7-445e-98ec-697694478b1c';
let subject = "You've unlocked level 100!";
let content = {
  rewardCoins: 1000,
}
let code = 101;
let senderId = 'dcb891ea-a311-4681-9213-6741351c9994' // who the message if from
let persistent = true;

nk.notificationSend(receiverId, subject, content, code, senderId, persistent);
```
{{< / code >}}

## 接收通知

可以为客户端连接时接收到的通知注册回调。只要客户端套接字保持连接，一旦接收到通知就会调用处理程序。当返回多条消息（进行批处理以提高性能）时，将为每条通知调用一次处理程序。

{{< code type="client" >}}
```javascript
socket.onnotification = (notification) => {
  console.log("Received %o", notification);
  console.log("Notification content %s", notification.content);
}
```
{{< / code >}}

{{< code type="client" >}}
```csharp
socket.ReceivedNotification += notification =>
{
    Console.WriteLine("Received: {0}", notification);
    Console.WriteLine("Notification content: '{0}'", notification.Content);
};
```
{{< / code >}}

{{< code type="client" >}}
```cpp
rtListener->setNotificationsCallback([](const NNotificationList& notifications)
{
    for (auto& notification : notifications.notifications)
    {
        std::cout << "Notification content " << notification.content << std::cout;
    }
});
```
{{< / code >}}

{{< code type="client" >}}
```java
SocketListener listener = new AbstractSocketListener() {
    @Override
    public void onNotifications(final NotificationList notifications) {
        System.out.println("Received notifications");
        for (Notification notification : notifications.getNotificationsList()) {
            System.out.format("Notification content: %s", notification.getContent());
        }
    }
};
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
func _ready():
    # First, setup the socket as explained in the authentication section.
    socket.connect("received_notification", self, "_on_notification")

func _on_notification(p_notification : NakamaAPI.ApiNotification):
    print(p_notification)
    print(p_notification.content)
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local group_id = "<group id>"
socket.on_notification(function(message)
  print("Received notification!")
  pprint(message)
end)
```
{{< / code >}}

{{< missing type="client" lang="shell" />}}
{{< missing type="client" lang="bash" />}}

## 列出通知

您可以列出用户离线时接收到的通知。这些通知在发送时被标记为“持久”。实际的逻辑取决于游戏或应用程序，但我们建议在客户端重新连接后检索通知。然后您可以在游戏或应用程序内显示 UI 以及此列表。

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/notification?limit=10" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const result = await client.listNotifications(session, 10);
result.notifications.forEach(notification => {
  console.info("Notification code %o and subject %o.", notification.code, notification.subject);
});
console.info("Fetch more results with cursor:", result.cacheable_cursor);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var result = await client.ListNotificationsAsync(session, 10);
foreach (var n in result.Notifications)
{
    Console.WriteLine("Subject '{0}' content '{1}'", n.Subject, n.Content);
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NNotificationListPtr list)
{
    for (auto& notification : list->notifications)
    {
        std::cout << "Notification content " << notification.content << std::endl;
    }
};

client->listNotifications(session, 10, opt::nullopt, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
NotificationList notifications = client.listNotifications(session, 10).get();
for (Notification notification : notifications.getNotificationsList()) {
    System.out.format("Notification content: %s", notification.getContent());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var result : NakamaAPI.ApiNotificationList = yield(client.list_notifications_async(session, 10), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for n in result.notifications:
    print("Subject '%s' content '%s'" % [n.subject, n.content])
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/notification?limit=10
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.list_notifications(10)
if result.error then
  print(result.message)
  return
end
for _,notification in ipairs(result.notifications) do
  pprint(notification)
end
```
{{< / code >}}

一次可以成批检索 100 条以内通知的列表。要检索所有消息，应使用可缓存游标累积消息。您可以将此游标保留在客户端上，并在用户重新连接时使用它，以跟踪离线时他们可能错过的任何通知。

您一般需要一次仅列出 100 条通知，否则可能会导致用户疲劳。一个好的解决方案是当用户滚动到 UI 面板的底部时，让 UI 获取下 100 条通知。

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/notification?limit=100&cacheable_cursor=<cacheableCursor>" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var allNotifications = [];

var accumulateNotifications = (cursor) => {
  var result = await client.listNotifications(session, 100, cursor);
  if (result.notifications.length == 0) {
    return;
  }
  allNotifications.concat(result.notifications.notifications);
  accumulateNotifications(result.cacheable_cursor);
}
accumulateNotifications("");
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var result = await client.ListNotificationsAsync(session, 100);
if (!string.IsNullOrEmpty(result.CacheableCursor))
{
    result = await client.ListNotificationsAsync(session, 100, result.CacheableCursor);
    foreach (var n in result.Notifications)
    {
        Console.WriteLine("Subject '{0}' content '{1}'", n.Subject, n.Content);
    }
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
// add to your class: std::vector<NNotification> allNotifications;

void YourClass::accumulateNotifications(const string& cursor)
{
    auto successCallback = [this](NNotificationListPtr list)
    {
        allNotifications.insert(allNotifications.end(), list->notifications.begin(), list->notifications.end());

        if (!list->cacheableCursor.empty())
        {
            accumulateNotifications(list->cacheableCursor);
        }
    };

    client->listNotifications(session, 100, cursor, successCallback);
}

accumulateNotifications("");
```
{{< / code >}}

{{< code type="client" >}}
```java
NotificationList notifications = client.listNotifications(session, 10).get();
if (notifications.getCacheableCursor() != null) {
    notifications = client.listNotifications(session, 10, notifications.getCacheableCursor()).get();
    for (Notification notification : notifications.getNotificationsList()) {
        System.out.format("Notification content: %s", notification.getContent());
    }
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var result : NakamaAPI.ApiNotificationList = yield(client.list_notifications_async(session, 1), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for n in result.notifications:
    print("Subject '%s' content '%s'" % [n.subject, n.content])
while result.cacheable_cursor and result.notifications:
    result = yield(client.list_notifications_async(session, 10, result.cacheable_cursor), "completed")
    if result.is_exception():
          print("An error occurred: %s" % result)
          return
    for n in result.notifications:
          print("Subject '%s' content '%s'" % [n.subject, n.content])
print("Done")
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/notification?limit=100&cursor=<cacheableCursor>
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 cursor = nil
repeat
  local result = client.list_notifications(10, cursor)
  if result.error then
      print(result.message)
      break
  end

  cursor = result.cursor
  for _,notification in ipairs(result.notifications) do
      pprint(notification)
  end
until not cursor
```
{{< / code >}}

这对于仅检索自上次客户端检索列表以来添加的通知很有用。可以用随每条列表消息返回的可缓存游标做到这一点。通过新的列表操作发送游标，将仅检索比已出现通知更晚的通知。

可缓存游标标记最近检索的通知的位置。我们建议您将可缓存游标存储在设备存储器中，并在客户端下次请求最近的通知时使用它。

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/notification?limit=10&cacheable_cursor=<cacheableCursor>" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const cacheableCursor = "<cacheableCursor>";
const result = await client.listNotifications(session, 10, cacheableCursor);
result.notifications.forEach(notification => {
  console.info("Notification code %o and subject %o.", notification.code, notification.subject);
});
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string cacheableCursor = "<cacheableCursor>";
var result = await client.ListNotificationsAsync(session, 10, cacheableCursor);
foreach (var n in result.Notifications)
{
    Console.WriteLine("Subject '{0}' content '{1}'", n.Subject, n.Content);
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NNotificationListPtr list)
{
    for (auto& notification : list->notifications)
    {
        std::cout << "Notification content " << notification.content << std::endl;
    }
};

string cacheableCursor = "<cacheableCursor>";
client->listNotifications(session, 10, cacheableCursor, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String cacheableCursor = "<cacheableCursor>";
NotificationList notifications = client.listNotifications(session, 10, cacheableCursor).get();
for (Notification notification : notifications.getNotificationsList()) {
    System.out.format("Notification content: %s", notification.getContent());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var cursor = "<cacheable-cursor>";
var result : NakamaAPI.ApiNotificationList = yield(client.list_notifications_async(session, 10, cursor), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for n in result.notifications:
    print("Subject '%s' content '%s'" % [n.subject, n.content])
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/notification?limit=10&cursor=<cacheableCursor>
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 cursor = "<cacheable-cursor>"
local result = client.list_notifications(10, cursor)
if result.error then
  print(result.message)
  return
end

for _,notification in ipairs(result.notifications) do
  pprint(notification)
end
```
{{< / code >}}

## 删除通知

您可以从客户端删除一条或多条通知。这有助于清除用户已读取或使用的通知，并防止旧消息的积累。删除通知后，会从系统中删除该通知的所有记录，并且无法恢复该通知。

{{< code type="client" >}}
```bash
curl -X DELETE "http://127.0.0.1:7350/v2/notification?ids=<notificationId>&ids=<notificationId>" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const notificationIds = ["<notificationId>"];
await client.deleteNotifications(session, notificationIds);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var notificationIds = new[] {"<notificationId>"};
await client.DeleteNotificationsAsync(session, notificationIds);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
client->deleteNotifications(session, { "<notificationId>" });
```
{{< / code >}}

{{< code type="client" >}}
```java
String[] notificationIds = new String[] {"<notificationId>"};
client.deleteNotifications(session, notificationIds).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var notification_ids = ["<notification-id>"]
var delete : NakamaAsyncResult = yield(client.delete_notifications_async(session, notification_ids), "completed")
if delete.is_exception():
    print("An error occurred: %s" % delete)
    return
print("Deleted")
```
{{< / code >}}

{{< code type="client" >}}
```shell
DELETE /v2/notification?ids=<notificationId>&ids=<notificationId>
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 = { "<notification-id>" }
local result = client.delete_notifications(ids)
if result.error then
  print(result.message)
  return
end
```
{{< / code >}}

## 通知代码

对于就某些事件隐式发送的消息，服务器保留所有小于或等于 0 的代码。您只需使用大于 0 的值即可定义自己的通知代码。

该代码有助于决定如何在 UI 中显示通知。

| 代码 | 用途 |
|------| ------- |
| 0    | 保留 |
| -1   | 离线或不在频道中时接收到的用户 X 的消息。 |
| -2   | 用户 X 想要将您添加为好友。  |
| -3   | 用户 X 接受了您的好友邀请。 |
| -4   | 您已被接收进 X 群组。 |
| -5   | 用户 X 想要加入您的群组。 |
| -6   | 您的好友 X 加入了游戏。 |
| -7   | 到套接字的最后通知通过[`single_socket` 配置](../../getting-started/configuration/#session) 关闭。 |
