> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/long-term-memory/end-user-identity.md).

# 终端用户身份

了解 SDK 如何识别用于长期记忆作用域的用户，以及如何实现基于自定义认证的身份提供器。

每个长期记忆会话都需要一个稳定的 `end_user_id` ——SDK 会在每次连接时将一个字符串发送到 Convai。Convai 使用它来限定已存储记忆的范围：从对话中提取的事实会存储在以下组合之下： `end_user_id` 和 `character_id`。如果该标识符在会话之间发生变化，服务器会将用户视为新用户，且不会沿用任何记忆。

SDK 提供 `DeviceEndUserIdProvider` 作为无需配置的默认值。对于带有用户身份验证的应用，你可以将其替换为一个返回你的账户 ID 的自定义提供程序。

***

### 默认身份： `DeviceEndUserIdProvider`

`DeviceEndUserIdProvider` 同时实现 `IEndUserIdProvider` 和 `IEndUserIdentityProvider`。它会自动注册——无需配置。

**在 Unity 编辑器中：** 始终读取或创建存储在 `PlayerPrefs` 中的 GUID，键名为 `"convai.end_user_id"`。同一台机器上的每个播放模式会话都会共享此 GUID，因此在开发和测试期间记忆会按预期累积。

**在玩家构建中：** 首先尝试 `SystemInfo.deviceUniqueIdentifier`。如果该值不可用、为空、等于 `SystemInfo.unsupportedIdentifier`，或者完全由 0 组成，则会回退到 `PlayerPrefs` GUID——它只生成一次，并在该设备上的所有后续会话中重复使用。

GUID 的格式为不含连字符的 32 位十六进制字符串（例如， `a1b2c3d4e5f6789012345678abcdef01`）。你会在控制台日志以及检查 `EndUserDetails.EndUserId`.

`PlayerPrefs` 不会在重新安装后保留。清除 `PlayerPrefs` 或重新安装应用都会生成一个新的 GUID。Convai 会将新的 GUID 视为新用户——所有先前存储的记忆在新 ID 下都将无法访问（不过它们仍保留在服务器上旧 ID 下）。对于需要在重新安装后保持数据连续性的应用，请通过自定义提供程序使用服务器分配的账户 ID。参见 [实现自定义身份提供程序](#implement-a-custom-identity-provider) 下文。

***

### Player ID 不是最终用户 ID

**在 `ConvaiPlayer` 组件上设置 Player ID 不会改变 `end_user_id` 用于记忆范围划分。** Player ID 只控制本地玩家名称在转录和调试日志中的显示方式。记忆范围始终由 `IEndUserIdentityProvider`决定，而不是由 `ConvaiPlayer`.

***

### 实现自定义身份提供程序

如果你的应用对用户进行身份验证，请实现 `IEndUserIdentityProvider` 以返回一个稳定的、由服务器分配的账户标识符。这样可确保记忆能随用户跨设备和重新安装延续。

#### 实现该接口

```csharp
using Convai.Domain.Identity;

public class AccountIdentityProvider : IEndUserIdentityProvider
{
    private readonly string _accountId;

    public AccountIdentityProvider(string accountId)
    {
        _accountId = accountId;
    }

    public string GetEndUserId()
    {
        return _accountId;
    }
}
```

请使用你的后端分配的账户 ID 作为标识符。不要使用电子邮件地址或显示名称——它们可能会变化，这会导致服务器将用户视为新用户。

#### 可选附加元数据

实现 `IEndUserMetadataProvider` 以便随身份一并发送显示信息。Convai 会将此元数据存储在 `EndUserDetails.Metadata` 并使用 `"name"` 键来填充编辑器的长期记忆面板中的显示名称。

```csharp
using System.Collections.Generic;
using Convai.Domain.Identity;

public class AccountMetadataProvider : IEndUserMetadataProvider
{
    private readonly string _displayName;
    private readonly string _department;

    public AccountMetadataProvider(string displayName, string department)
    {
        _displayName = displayName;
        _department = department;
    }

    public IReadOnlyDictionary<string, object> GetEndUserMetadata()
    {
        return new Dictionary<string, object>
        {
            { "name", _displayName },
            { "department", _department }
        };
    }
}
```

#### 在首次连接前注册

调用 `SetEndUserIdentityProvider` 和 `SetEndUserMetadataProvider` 在 `ConvaiManager` **在首次会话连接之前**。如果 `ConvaiCharacter` 已勾选 **自动连接** 启用后，连接会在 `ConvaiRoomManager.Start()` 完成后——请在 `Awake()`，而不是 `Start()`中注册你的提供程序，以确保顺序正确。

```csharp
using Convai.Runtime.Components;
using UnityEngine;

public class IdentityRegistrar : MonoBehaviour
{
    [SerializeField] private ConvaiManager _convaiManager;

    private void Awake()
    {
        // 在 ConvaiRoomManager.Start() 之前调用——对自动连接角色安全
        string accountId = AuthService.CurrentUser.AccountId;
        string displayName = AuthService.CurrentUser.DisplayName;
        string department = AuthService.CurrentUser.Department;

        _convaiManager.SetEndUserIdentityProvider(new AccountIdentityProvider(accountId));
        _convaiManager.SetEndUserMetadataProvider(new AccountMetadataProvider(displayName, department));
    }
}
```

{% hint style="danger" %}
如果你调用 `SetEndUserIdentityProvider` 在 `ConnectAsync` 已经在当前会话中被调用过，则提供程序更改不会影响当前活动连接。新的提供程序将从下一次 `ConnectAsync` 调用开始生效。请始终在连接前注册。
{% endhint %}

***

### 空的或仅包含空白字符的最终用户 ID

{% hint style="danger" %}
如果 `GetEndUserId()` 返回空字符串、仅包含空白字符的字符串，或 `null`，SDK 会将其规范化为 `null` 后再发送。Convai 会将 `null` 视为匿名会话—— **不会存储或检索任何记忆**。请始终确保你的身份来源返回非空且不全为空白的值。
{% endhint %}

***

### 身份来源对比

| 场景        | 推荐来源                           | 重新安装后仍可保留 | 更换设备后仍可保留 |
| --------- | ------------------------------ | --------- | --------- |
| 开发 / 测试   | `DeviceEndUserIdProvider` （默认） | 没有        | 没有        |
| 消费级应用，无账户 | `DeviceEndUserIdProvider` （默认） | 没有        | 没有        |
| 带账户的消费级应用 | 自定义提供程序 → 服务器账户 ID             | 是         | 是         |
| 企业 / 培训平台 | 自定义提供程序 → 服务器账户 ID             | 是         | 是         |

***

### 下一步

{% content-ref url="/pages/49cef55d22f33c0ffdf4893701cd5a6686bd7590" %}
[管理终端用户记录](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/long-term-memory/end-user-management.md)
{% endcontent-ref %}

{% content-ref url="/pages/0b7e1084a14c3bcf13fec179ad83b458f07ecff4" %}
[记忆管理 API](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/long-term-memory/memory-management-api.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/long-term-memory/end-user-identity.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
