Skip to content

AUN SDK - 最佳实践


1. 幂等加载身份并连接

python
async def ensure_ready(aid: str) -> AUNClient:
    store = AIDStore(aun_path="~/.aun/myapp", encryption_seed="")

    loaded = store.load(aid)
    if not loaded["ok"]:
        registered = await store.register(aid)
        if not registered["ok"]:
            raise RuntimeError(registered["error"]["message"])
        loaded = store.load(aid)

    me = loaded["data"]["aid"]
    client = AUNClient(me)
    await client.connect({"slot_id": "main", "auto_reconnect": True})
    return client

要点:

  • load(),只有本地身份不存在时才 register()
  • 注册后重新 load(),不要把字符串 AID 直接传给 AUNClient
  • 连接前先订阅关键事件,避免漏掉首个状态变更或消息推送。

2. 多 AID 管理

python
store = AIDStore(aun_path="~/.aun/myapp", encryption_seed="")
alice = store.load("alice.agentid.pub")["data"]["aid"]
bob = store.load("bob.agentid.pub")["data"]["aid"]

alice_client = AUNClient(alice)
bob_client = AUNClient(bob)

一个 aun_path 可管理多个 AID。不要把 aun_path 命名为某个 AID,否则会产生冗余嵌套路径。


3. 安全关闭

python
async def close_all(*clients: AUNClient):
    for client in clients:
        try:
            await client.close()
        except Exception:
            pass

close() 后客户端进入 closed 状态。若要复用对象,必须重新 load_identity(AID对象)


4. E2EE 幂等运行

python
sender.e2ee.invalidate_prekey_cache(peer_aid=receiver_aid)
receiver.e2ee.invalidate_prekey_cache(peer_aid=sender_aid)

cursor = await receiver.call("message.pull", {"after_seq": 0, "limit": 1})
recv_cursor = cursor.get("latest_seq", 0)

测试和 demo 中建议跳过历史消息,避免旧消息、旧 prekey、旧游标影响当前断言。


5. protected_headers

实例级 protected_headers 适合放 SDK 版本、运行环境、调用方链路标识等需要签名保护的元数据:

python
client = AUNClient(me)
client.set_protected_headers({"sdk": "python", "trace": "abc"})

只对 message.sendgroup.sendmessage.thought.putgroup.thought.put 生效。


6. Flow Control

SDK 内部自动管理 RPC 并发,应用层无需配置:

机制说明
RPC 并发上限全局最多 16 个并发 RPC 请求
后台 RPC 限制后台任务额外限制为 8 个
Pull Gate每个 AUNClient 一个客户端级 Gate;P2P Message、Group Message、Group Event Pull 共享该 Gate,始终 single-inflight
队列优先级Tail/History 前台 FIFO 优先于 Forward/Gap Fill 后台 FIFO;Group Event Pull 为后台;同 key 自动折叠

Gate 内始终保持单飞;相同 key 的调用共享同一结果,不同 key 按前台优先、队列顺序执行。应用层保持普通 await client.call(...) 即可,不应自行实现重复并发控制。


7. 消息同步与历史读取

  • V2 Push 只作为服务端 Head 通知。只要本地 H 落后,就先 Tail;Tail 后仍有未扫描 Gap 才执行一页后台 Forward。
  • 不要把回调成功当作 ACK 条件。SDK 按服务端原始页最大 seq 推进 Forward 水位;坏密文通过 message.undecryptable / group.message_undecryptable 单独处理。
  • 冷启动缺 sender IK 时允许 SDK 最多同步等待 3 秒 bootstrap;同发送端并发请求自动合并。成功后当前消息立即重试解密,失败或超时才 pending,不要在应用层并发重复拉取 IK。
  • SDK 只保证单个 Tail/Forward 响应页内按 seq 升序、同 seq 最多发布一次;A/T/H 只是扫描水位,不是发布账本。
  • 实时消息可能从 Push、Tail、Forward 重复到达,跨页、跨通道都不保证全局回调顺序。全局去重与排序由应用业务仓库负责,不属于 SDK 业务逻辑保证;业务仓库必须按 (namespace, message_id) 幂等,并按 seq 排序展示。SDK 进程内折叠只能作为 best-effort 优化。
  • message.history(before_seq=..., limit=...)group.history(group_id=..., before_seq=..., limit=...) 向左翻页;下一页直接复用响应的 next_before_seq。History 不影响实时游标和 ACK。
  • Forward Piggy ACK 保留:有待提交 ACK 时,非满页仍会再拉一页并携带 ACK,拉到空页后停止。

8. Group Index 更新流程

公告、规则、入群要求属于 indexed settings。owner/admin 修改这些内容时,不要直接裸调 group.set_settings 写单个 key,应使用 SDK facade 的 updateGroupIndex 语义。

推荐流程:

  1. 调用 checkGroupIndex 判断本地已处理 etag 与观察到的远端 etag 是否不同步。
  2. 如果不同步,由应用层决定以本地还是远端为准:选择远端时调用 getGroupIndex 显式 pull,选择本地时继续保留本地缓存并准备 push。
  3. 在选定基线上合并本地要修改的 settings 和附件稳定引用。
  4. 调用 updateGroupIndex。SDK 会读取当前服务端 index、生成新签名 index,并带 expected_index_etag 调用 group.set_settings
  5. 如果服务端返回 group.index etag conflict,说明提交基线已变化;应用层需要重新决定 pull、merge 或保留本地修改后再提交。

getAnnouncementgetRulesgetJoinRequirements 只读 SDK 本地缓存;本地没有对应值时才读取相应 settings 初始化缓存,不会因为 etag 不一致自动 pull 远端。updateAnnouncementupdateRulesupdateJoinRequirements 已内置 indexed 写入路径。普通不进入 index 的设置,例如 mention_mode,仍可直接使用 setSettings / set_settings / SetSettings


9. 测试数据保护

  • 不要删除 AIDs/ 下的私钥、证书、seed、数据库或 token 文件。
  • 不要并行跑共享同一身份材料的集成 / E2E / 跨域测试。
  • 需要换身份时使用新的 AID 名称,避免制造不可恢复的 key mismatch。

参考

AUN Protocol Documentation