> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wuji.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Wuji SDK 本地用户管理

## 概述

本篇实践介绍如何创建、切换和删除 SDK 本地用户，并管理标定数据迁移。应用场景包括多人共用设备时隔离标定数据和在不同机器间迁移个人配置。

对应的 Python 示例为 [4.user.py](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_glove/4.user.py)，C 示例为 [3\_user\_management.c](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/c/wuji_glove/3_user_management.c)。 接口与参数见 [SDK 用户管理](/docs/wuji-sdk/device-params#sdk-用户管理) 和 [用户数据导入导出](/docs/wuji-sdk/device-params#用户数据导入导出)。

## 前置条件

### 硬件

用户管理只读写本地数据，不需要连接手套。

### 软件

| 项目 | 要求 |
| - | - |
| 操作系统 | Ubuntu 22.04 LTS 及以上版本 |
| Wuji SDK | v2026.8.31 及更高版本（本文验证所用版本） |
| Python | 3.10 及以上版本 |
| C 工具链 | CMake 3.16 及以上版本，以及解压后的 SDK 包 |

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    pip install wuji-sdk
    ```
  </Tab>

  <Tab title="C">
    ```bash theme={null}
    cmake -S examples/c/wuji_glove -B build/glove \
      -DWUJI_SDK_INCLUDE_DIR="${SDK}/include" \
      -DWUJI_SDK_LIB="${SDK}/lib/libwuji_sdk_c.so"
    cmake --build build/glove --target 3_user_management
    ```
  </Tab>
</Tabs>

## 数据流

```mermaid theme={null}
flowchart LR
    APP["Python SdkManager<br/>或 C wuji_user_*"] --> READ["current_user() / list_users()"]
    READ --> EXIST{"找到目标用户？"}
    EXIST -->|是| UPDATE["可选 update_user()"]
    EXIST -->|否| CREATE["create_user()"]
    UPDATE --> SWITCH["switch_user(user_id)"]
    CREATE --> SWITCH
    SWITCH --> REG["users/registry.toml<br/>持久化当前用户指针"]
    REG --> CAL["后续标定数据<br/>写入当前用户与设备目录"]
```

## 本地存储布局

SDK 使用 `~/.wuji/sdk/` 保存以下数据：

```text theme={null}
~/.wuji/sdk/
├── storage.json                    # SDK 级状态
├── users/
│   ├── registry.toml               # 用户注册表 + 当前用户指针
│   └── <user_id>/                  # 每个用户的私有目录
├── params/
│   └── <序列号>.toml               # 按手套序列号管理的参数
└── models/
    └── <序列号>_hand.urdf          # 标定产出的手部模型
```

`registry.toml` 示例：

```toml theme={null}
current_user_id = "u_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

[[users]]
user_id = ""
display_name = "Default"
is_default = true
created_at = "2026-07-14T13:05:53.428368166+00:00"
updated_at = "2026-07-14T13:05:53.428368166+00:00"

[[users]]
user_id = "u_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
display_name = "alice"
is_default = false
created_at = "2026-07-14T13:05:53.428376205+00:00"
updated_at = "2026-07-14T13:05:53.428376205+00:00"
```

默认用户的 `user_id` 为空字符串，不是 `"default"` 或 `None`。`current_user_id` 保存在此文件中，因此用户切换会持久化。

## API 参考

下表列出用户管理相关接口：

| 作用 | Python | C |
| - | - | - |
| 看当前用户 | `current_user()` | `wuji_current_user(&out)` |
| 列出全部 | `list_users()` | `wuji_list_users(&arr, &len)` |
| 建用户 | `create_user(display_name, description=None)` | `wuji_create_user(name, desc, external_id, &out)` |
| 改信息 | `update_user(user_id, description=...)` | `wuji_update_user(user_id, &update, &out)` |
| 切换 | `switch_user(user_id)` | `wuji_switch_user(user_id, &out)` |
| 切回默认 | `switch_to_default_user()` | `wuji_switch_to_default_user(&out)` |
| 删除 | `delete_user(user_id)` | `wuji_delete_user(user_id)` |
| 导出标定包 | `export_user_data(user_id, out_path)` | `wuji_user_data_export(user_id, out_zip_path)` |
| 预览标定包 | `preview_user_data(bundle_path)` | `wuji_user_data_preview(zip_path)` |
| 导入标定包 | `import_user_data(...)` | `wuji_user_data_import(zip_path)` |

Python 和 C 提供对应的用户管理能力。导出、预览和导入接口用于在不同电脑之间迁移标定数据。

Python 侧用户 dict 包含以下字段： `user_id`、`display_name`、`description`、`is_default`、`created_at`、`updated_at`、`external_id`。C 侧对应 `WujiUserInfo` 结构体，字段同名。

C API 返回的 `WujiUserInfo` 字符串由 SDK 分配。单个结构体使用 `wuji_user_info_free(&user)` 释放，数组使用 `wuji_user_info_array_free(users, len)` 释放。不要混用这两个函数。

`WujiUserUpdate` 除了 `display_name`、`description`、`external_id` 三个字段，还有 `clear_description` 与 `clear_external_id` 两个布尔位，用来把字段显式置空——只传 NULL 表示「不改」，不表示「清空」。

## 集成步骤

先确认当前用户，再选择或创建用户，最后核对切换结果。用户操作不需要连接设备。

### 1. 查看当前用户

读取当前用户，确认接下来的标定数据将归属谁。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from wuji_sdk import SdkManager
    manager = SdkManager.instance()
    previous = manager.current_user()
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    WujiInitOptions opts = { .log_level = 2 };
    if (wuji_init(&opts) != WUJI_STATUS_OK) return 1;
    WujiUserInfo previous = {0};
    if (wuji_current_user(&previous) != WUJI_STATUS_OK) {
        fprintf(stderr, "%s\n", wuji_last_error());
        wuji_shutdown();
        return 1;
    }
    ```
  </Tab>
</Tabs>

### 2. 选择或创建用户

按显示名称查找用户，已有用户直接复用，找不到时创建。下文 Python 的 `find_user_by_display_name` 和 C 的 `switch_or_create_user`、`ExampleArgs` 均来自 [Python 示例](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_glove/4.user.py) 和 [C 示例](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/c/wuji_glove/3_user_management.c)，并非 SDK 接口。多个同名用户会触发错误，处理重名后再切换。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    name = "Alice"
    user = find_user_by_display_name(manager, name)
    if user is None:
        user = manager.create_user(name, description="right hand")
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    ExampleArgs args = {
        .user_name = "Alice",
        .user_description = "right hand",
    };
    WujiUserInfo current = {0};
    if (switch_or_create_user(&args, &current) != 0) {
        wuji_user_info_free(&previous);
        wuji_shutdown();
        return 1;
    }
    ```
  </Tab>
</Tabs>

C 辅助函数依次调用 `wuji_list_users`、按需调用 `wuji_create_user` 或 `wuji_update_user`，再调用 `wuji_switch_user`。函数释放查询列表和临时用户信息，返回切换后的 `current`。

### 3. 切换并核对

Python 使用 `user_id` 切换，C 在上一步已完成切换。核对返回的用户信息后，再进入标定流程。切换会持久保存当前用户，后续进程也使用选中的用户。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    current = manager.switch_user(user["user_id"])
    print(current["display_name"], current["user_id"])
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    printf("%s (%s)\n", current.display_name, current.user_id);
    ```
  </Tab>
</Tabs>

### 4. 释放资源

Python 的用户字典由运行时管理。C 程序结束时释放用户信息并关闭 SDK。释放查询结果不会删除用户或标定数据。

```c theme={null}
wuji_user_info_free(&previous);
wuji_user_info_free(&current);
wuji_shutdown();
```

## 运行示例

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    python examples/python/wuji_glove/4.user.py --list
    python examples/python/wuji_glove/4.user.py --user-name Alice --user-description "right hand"
    python examples/python/wuji_glove/4.user.py --default-user
    ```
  </Tab>

  <Tab title="C">
    ```bash theme={null}
    ./build/glove/3_user_management --list
    ./build/glove/3_user_management --user-name Alice --user-description "right hand"
    ./build/glove/3_user_management --default-user
    ```
  </Tab>
</Tabs>

C 侧 `--list` 的实测输出：

```text theme={null}
previous current user:
  user_id:      'u_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
  display_name: alice
  description:  None
  is_default:   False
current SDK user:
  user_id:      'u_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
  display_name: alice
  description:  None
  is_default:   False

SDK users:
  Default user_id='' description=None
* alice user_id='u_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' description=None
```

创建新用户时 `previous` 与 `current` 两段会不同：

```text theme={null}
previous current user:
  display_name: alice
current SDK user:
  user_id:      'u_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
  display_name: CDocTest
  description:  C 侧验证
  is_default:   False
```

两侧输出格式完全一致，示例做了对齐。`*` 标记当前用户。

示例使用 Wuji SDK v2026.8.31，运行时不需要连接设备。

## 行为与错误处理

以下行为适用于用户管理接口。

### 用户名必须唯一

```text theme={null}
WujiException: Internal error: SDK user display_name 'PracticeDoc' already exists
```

SDK 已保证 `display_name` 唯一。[Python 示例](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_glove/4.user.py) 中的重复匹配分支不会被触发：

```python theme={null}
matches = [u for u in manager.list_users() if u.get("display_name") == display_name]
if len(matches) > 1:
    raise RuntimeError(f"Multiple SDK users named {display_name!r}. ...")
```

调用方无需为重复结果保留额外分支。

创建用户时仍需处理重名错误。并发创建可能发生竞争，请捕获 `WujiException`。

### 删除当前用户

删除当前用户后，SDK 自动切换到默认用户：

| 操作 | 操作后的 `current_user()` |
| - | - |
| 切到 `PracticeDoc`，删除 `PracticeDoc` | `display_name='Default'`，`user_id=''`，`is_default=True` |

删除操作不会留下悬空的当前用户指针。该用户的标定数据也会被删除，请确认数据不再需要。

### 默认用户不可删除

```text theme={null}
WujiException: Internal error: default SDK user cannot be deleted
```

### 不存在的用户 ID 会触发错误

```text theme={null}
WujiException: Internal error: SDK user 'u_does_not_exist' not found
```

SDK 不会自动切换到默认用户。

### 用户切换在进程和语言之间生效

例如：

1. C 程序执行 `./3_user_management --default-user`，切到 Default
2. 启动一个全新的 Python 进程读 `current_user()`

结果为 `Default`，`user_id` 为 `''`。C 程序创建的用户也会立即出现在 Python 的 `list_users()` 结果中，`description` 字段保持不变：

```text theme={null}
用户列表: [('Default', None), ('alice', None), ('CDocTest', 'C 侧验证')]
```

两个语言接口读写同一份 `registry.toml`，不会维护独立缓存。

### 导出前需要标定数据

对未完成标定的用户调用 `export_user_data`：

```text theme={null}
WujiException: Internal error: no calibration data found for user alice
```

`export_user_data(user_id, out_path)` 的两个参数均为必填。

## 错误处理速查

| 分支点 | 表现 | 处理 |
| - | - | - |
| 创建时 display\_name 重复 | 抛 `WujiException` | 先用 `list_users()` 查询，或捕获异常后切换到已有用户 |
| 删除当前用户 | 自动切换到默认用户 | 确认标定数据不再需要 |
| 删除默认用户 | 抛 `WujiException` | 默认用户不可删除 |
| 切换到不存在的 user\_id | 抛 `WujiException` | 用 `list_users()` 核对 |
| 导出未标定用户 | 抛 `WujiException` | 先完成标定 |
| 多进程同时修改用户 | 后写入的值覆盖先写入的值 | 避免并发修改用户 |

命令行工具可以捕获 `WujiException` 并以非零退出码结束。向用户显示异常消息即可，无需输出完整堆栈。

## 扩展用法

### 标定之前先确认用户

标定前先显示当前用户，确认标定数据写入正确的用户：

```python theme={null}
print(f"标定将写入用户: {SdkManager.instance().current_user()['display_name']}")
```

### 迁移标定结果到另一台机器

`export_user_data` 打包，`preview_user_data` 在目标机上先看内容，确认无误再 `import_user_data`。导入会按需重建用户，不用先手工建好同名用户。

### 用 external\_id 关联外部系统

用户 dict 里有 `external_id` 字段，示例没用到。如果需要关联外部账号系统，请使用 `external_id` 保存外部主键，不要使用 `display_name` 作为唯一关联键。

### 脚本化时避开交互

两个示例都是命令行工具。`--list` 成功时返回 0，失败时返回 1，可用于脚本判断。

## 相关内容

* [Wuji Glove 手部模型标定](/docs/wuji-glove/sdk-practice/hand-model-calibration/)：产出挂在当前用户名下的手部模型
* [Wuji Glove 触觉标定](/docs/wuji-glove/sdk-practice/tactile-calibration/)：同样按 SDK 用户与手套序列号进行管理
* [Wuji Glove 设置自定义手部模型](/docs/wuji-glove/sdk-practice/custom-hand-model/)：绕过用户体系直接指定手模
* [Wuji Glove 离线数据处理](/docs/wuji-glove/sdk-practice/offline-replay/)：重放时按序列号加载对应手模


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.