> 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"`。同一台机器上的每个 Play Mode 会话都会共享此 GUID，因此在开发和测试期间记忆会按预期累积。

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

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

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

***

### Player ID 不是 end-user 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() 之前调用——对启用 Auto Connect 的角色安全
        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 %}

***

### 空的或仅空白的 end-user 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
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=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
