<template>
  <div class="help-content">
    <div class="content-header">
      <h2>客户端接口 API 指南</h2>
      <el-button type="primary" size="small" plain @click="downloadDoc('/docs/client_api_guide.md', 'Client_API_Guide.md')">
        <el-icon style="margin-right: 5px"><Download /></el-icon>
        下载 开发文档
      </el-button>
    </div>
    <p class="intro">
      本文档面向客户端（Web/移动端），说明消息接收、私信、通知、会话聚合、WebSocket 通信、当前用户信息（含组织）、联系人查询、应用中心（快捷导航）、<strong>平台账号管理场景下查询当前可访问的应用</strong>、个人身份二维码、短信验证码重置密码（忘记密码，开放接口）等接口。
    </p>

    <div class="section">
      <h3>1. 认证与调用约定</h3>
      <ul>
        <li><strong>认证方式</strong>：使用 <code>Authorization: Bearer &lt;JWT_TOKEN&gt;</code>。</li>
        <li><strong>接口前缀</strong>：统一使用 <code>/api/v1</code>。</li>
        <li><strong>错误处理</strong>：建议统一处理 <code>401/403/404/422</code>，并展示业务友好提示。</li>
      </ul>
    </div>

    <div class="section">
      <h3>2. 接收消息（拉取 + 推送）</h3>
      <p><strong>推荐用法（端到端）</strong></p>
      <ol>
        <li><strong>WebSocket 只负责提醒</strong>：长连收到 <code>NEW_MESSAGE</code> 后，不要只靠推送拼 UI；应触发<strong>对话列表刷新</strong>。</li>
        <li><strong>刷新列表时建议请求两个 HTTP 接口</strong>：① <code>GET /messages/unread-count</code> 获取<strong>全局未读总数</strong>（角标）；② <code>GET /messages/conversations</code> 获取<strong>会话列表</strong>（每行含 <code>unread_count</code>）。可并行发起。</li>
        <li><strong>用户点击某一会话</strong>：用该行的 <code>user_id</code> 作为 <code>other_user_id</code>，调用 <code>GET /messages/history/{other_user_id}</code> 拉聊天记录；进入会话后调用 <code>PUT /messages/history/{other_user_id}/read-all</code>，将该会话内你作为接收方的未读一次性标为已读（与产品「进入即已读」一致）。</li>
        <li><strong>用户点击「一键已读」</strong>：调用 <code>PUT /messages/read-all</code>，将整个收件箱未读全部标为已读（与按会话已读不同，见下表）。</li>
      </ol>
      <p>下列四个 HTTP 接口与「会话维度」共用路径参数 <code>other_user_id</code>（与 <code>conversations[].user_id</code> 相同）。认证均为 <code>Authorization: Bearer &lt;JWT_TOKEN&gt;</code>。</p>

      <h4>2.1 四个核心接口总览</h4>
      <table class="param-table">
        <thead>
          <tr><th>接口</th><th>作用</th></tr>
        </thead>
        <tbody>
          <tr><td><code>GET /api/v1/messages/unread-count</code></td><td>当前用户作为<strong>接收方</strong>的<strong>全局</strong>未读消息条数（整数）。</td></tr>
          <tr><td><code>GET /api/v1/messages/conversations</code></td><td>会话聚合列表，每行含 <code>user_id</code>、<code>unread_count</code>、<code>last_message</code>、<code>remarks</code> 等。</td></tr>
          <tr><td><code>GET /api/v1/messages/history/{other_user_id}</code></td><td>某一会话的聊天记录（分页）。</td></tr>
          <tr><td><code>PUT /api/v1/messages/history/{other_user_id}/read-all</code></td><td>仅将该会话范围内、你是接收方且未读的消息标为已读。</td></tr>
        </tbody>
      </table>

      <h4>2.2 <code>other_user_id</code>（路径参数，与 <code>conversations[].user_id</code> 一致）</h4>
      <table class="param-table">
        <thead>
          <tr><th>取值</th><th>含义</th></tr>
        </thead>
        <tbody>
          <tr><td><strong>&gt; 0</strong></td><td>与某用户的私信会话，值为对方用户 ID。</td></tr>
          <tr><td><strong>= 0</strong></td><td>兼容的「全部系统通知」视图（与 history 接口分支一致）。</td></tr>
          <tr><td><strong>&lt; 0</strong></td><td>某一应用的系统通知会话，值为 <code>-applications.id</code>（应用表主键取负）。</td></tr>
        </tbody>
      </table>

      <h4>2.3 <code>GET /api/v1/messages/unread-count</code></h4>
      <table class="param-table">
        <thead>
          <tr><th>项目</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><strong>方法 / 路径</strong></td><td><code>GET /api/v1/messages/unread-count</code></td></tr>
          <tr><td><strong>请求头</strong></td><td><code>Authorization: Bearer &lt;JWT_TOKEN&gt;</code></td></tr>
          <tr><td><strong>查询参数</strong></td><td>无</td></tr>
          <tr><td><strong>成功响应</strong></td><td><code>200</code>，响应体为<strong>纯整数</strong>（JSON number），表示你是接收方且 <code>is_read=false</code> 的消息总数（私信 + 通知合计）。</td></tr>
          <tr><td><strong>说明</strong></td><td>正式接口，用于角标等场景。</td></tr>
        </tbody>
      </table>
      <div class="code-block">
        <pre>
GET /api/v1/messages/unread-count HTTP/1.1
Host: your-domain.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...</pre>
      </div>
      <div class="code-block">
        <pre>
HTTP/1.1 200 OK
Content-Type: application/json

12</pre>
      </div>

      <h4>2.4 <code>GET /api/v1/messages/conversations</code></h4>
      <table class="param-table">
        <thead>
          <tr><th>项目</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><strong>方法 / 路径</strong></td><td><code>GET /api/v1/messages/conversations</code></td></tr>
          <tr><td><strong>请求头</strong></td><td><code>Authorization: Bearer &lt;JWT_TOKEN&gt;</code></td></tr>
          <tr><td><strong>查询参数</strong></td><td>无</td></tr>
          <tr><td><strong>成功响应</strong></td><td><code>200</code>，<code>Content-Type: application/json</code>，Body 为<strong>数组</strong>，元素为会话对象（见下表字段）。</td></tr>
          <tr><td><strong>说明</strong></td><td>服务端对与你相关的消息做内存聚合，有条数上限；极旧会话可能不出现在列表中。<code>unread_count</code> 仅统计「你是接收方且未读」。</td></tr>
        </tbody>
      </table>
      <p><strong>响应数组元素字段（ConversationResponse）</strong></p>
      <table class="param-table">
        <thead>
          <tr><th>字段</th><th>类型</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><code>user_id</code></td><td>number</td><td>会话键，与 <code>history/{other_user_id}</code> 的 <code>other_user_id</code> 相同。</td></tr>
          <tr><td><code>username</code></td><td>string</td><td>展示用账号（私信多为手机号；系统会话多为应用 ID 等）。</td></tr>
          <tr><td><code>full_name</code></td><td>string | null</td><td>展示名称。</td></tr>
          <tr><td><code>unread_count</code></td><td>number</td><td>该会话未读条数（仅你是接收方时计入）。</td></tr>
          <tr><td><code>last_message</code></td><td>string</td><td>最后一条预览文案。</td></tr>
          <tr><td><code>last_message_type</code></td><td>string</td><td>内容类型，如 <code>TEXT</code> / <code>IMAGE</code> / <code>USER_NOTIFICATION</code> 等。</td></tr>
          <tr><td><code>updated_at</code></td><td>string</td><td>该会话最后一条消息时间。</td></tr>
          <tr><td><code>is_system</code></td><td>boolean</td><td>是否系统/应用通知会话。</td></tr>
          <tr><td><code>app_id</code></td><td>number | null</td><td>应用主键（通知会话时有值）。</td></tr>
          <tr><td><code>app_name</code></td><td>string | null</td><td>应用名称。</td></tr>
          <tr><td><code>application_app_id</code></td><td>string | null</td><td>业务侧应用 ID（与 SSO、快捷导航一致）；按应用拆分的系统通知会话（通常 <code>user_id &lt; 0</code>）时有值，私信等为 <code>null</code>。</td></tr>
          <tr><td><code>icon_url</code></td><td>string | null</td><td>应用 Logo：对象存储时为短时预签名 URL；直链或未配置为可空。</td></tr>
          <tr><td><code>icon_object_key</code></td><td>string | null</td><td>对象存储时为稳定对象路径；可与 <code>application_app_id</code> 作本地缓存键，说明同 §8「快捷导航」<code>icon_object_key</code>。</td></tr>
          <tr><td><code>remarks</code></td><td>string | null</td><td>列表副文案：有 <code>app_id</code> 的应用通知为「应用通知」；无 <code>app_id</code> 的旧系统通知为 <code>null</code>；私信为对端组织名，无组织为 <code>null</code>。</td></tr>
        </tbody>
      </table>
      <div class="code-block">
        <pre>
GET /api/v1/messages/conversations HTTP/1.1
Authorization: Bearer &lt;JWT_TOKEN&gt;</pre>
      </div>
      <div class="code-block">
        <pre>
HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "user_id": -101,
    "username": "oa_system",
    "full_name": "OA系统",
    "unread_count": 3,
    "last_message": "您有一条待审批任务",
    "last_message_type": "TEXT",
    "updated_at": "2026-03-18T10:00:00",
    "is_system": true,
    "app_id": 101,
    "app_name": "OA系统",
    "application_app_id": "oa_system",
    "icon_url": "https://files.example.com/presigned/app_icon/oa.png?X-Amz-Expires=3600",
    "icon_object_key": "app_icon/101/a1b2c3d4-e5f6-7890-abcd-ef1234567890.png",
    "remarks": "应用通知"
  },
  {
    "user_id": 2048,
    "username": "13800138000",
    "full_name": "张三",
    "unread_count": 1,
    "last_message": "你好",
    "last_message_type": "TEXT",
    "updated_at": "2026-03-18T09:20:00",
    "is_system": false,
    "app_id": null,
    "app_name": null,
    "application_app_id": null,
    "icon_url": null,
    "icon_object_key": null,
    "remarks": "某某科技有限公司"
  }
]</pre>
      </div>

      <h4>2.5 <code>GET /api/v1/messages/history/{other_user_id}</code></h4>
      <table class="param-table">
        <thead>
          <tr><th>项目</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><strong>方法 / 路径</strong></td><td><code>GET /api/v1/messages/history/{other_user_id}</code></td></tr>
          <tr><td><strong>路径参数</strong></td><td><code>other_user_id</code>：整数，见上文 §2.2。</td></tr>
          <tr><td><strong>查询参数</strong></td><td><code>skip</code>（默认 0）、<code>limit</code>（默认 50），分页用。</td></tr>
          <tr><td><strong>请求头</strong></td><td><code>Authorization: Bearer &lt;JWT_TOKEN&gt;</code></td></tr>
          <tr><td><strong>成功响应</strong></td><td><code>200</code>，JSON 数组，元素为消息对象（MessageResponse，见 §2.7）。按创建时间降序（新消息在前）。</td></tr>
        </tbody>
      </table>
      <div class="code-block">
        <pre>
GET /api/v1/messages/history/2048?skip=0&amp;limit=50 HTTP/1.1
Authorization: Bearer &lt;JWT_TOKEN&gt;</pre>
      </div>
      <div class="code-block">
        <pre>
[
  {
    "id": 501,
    "sender_id": 2048,
    "receiver_id": 10001,
    "app_id": null,
    "app_name": null,
    "type": "MESSAGE",
    "content_type": "TEXT",
    "title": "私信",
    "content": "你好",
    "action_url": null,
    "action_text": null,
    "is_read": false,
    "created_at": "2026-03-18T09:59:00",
    "read_at": null
  }
]</pre>
      </div>

      <h4>2.6 <code>PUT /api/v1/messages/history/{other_user_id}/read-all</code></h4>
      <table class="param-table">
        <thead>
          <tr><th>项目</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><strong>方法 / 路径</strong></td><td><code>PUT /api/v1/messages/history/{other_user_id}/read-all</code></td></tr>
          <tr><td><strong>路径参数</strong></td><td><code>other_user_id</code>：与当前打开的会话 <code>user_id</code> 一致。</td></tr>
          <tr><td><strong>请求头</strong></td><td><code>Authorization: Bearer &lt;JWT_TOKEN&gt;</code></td></tr>
          <tr><td><strong>请求体</strong></td><td>无（不需 <code>Content-Type: application/json</code> 亦可）。</td></tr>
          <tr><td><strong>成功响应</strong></td><td><code>200</code>，JSON 对象：<code>{ "updated_count": &lt;number&gt; }</code>，表示本次更新行数。</td></tr>
          <tr><td><strong>说明</strong></td><td>只处理你是<strong>接收方</strong>且<strong>未读</strong>的记录；与 <code>history</code> 的会话范围一致。</td></tr>
        </tbody>
      </table>
      <div class="code-block">
        <pre>
PUT /api/v1/messages/history/2048/read-all HTTP/1.1
Authorization: Bearer &lt;JWT_TOKEN&gt;</pre>
      </div>
      <div class="code-block">
        <pre>
{ "updated_count": 3 }</pre>
      </div>

      <h4>2.7 可选：<code>PUT /api/v1/messages/read-all</code>（收件箱全部已读）</h4>
      <table class="param-table">
        <thead>
          <tr><th>项目</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><strong>方法 / 路径</strong></td><td><code>PUT /api/v1/messages/read-all</code></td></tr>
          <tr><td><strong>请求头</strong></td><td><code>Authorization: Bearer &lt;JWT_TOKEN&gt;</code></td></tr>
          <tr><td><strong>请求体</strong></td><td>无</td></tr>
          <tr><td><strong>成功响应</strong></td><td><code>200</code>，<code>{ "updated_count": &lt;number&gt; }</code>，将<strong>所有</strong>你是接收方的未读一次性标为已读。</td></tr>
          <tr><td><strong>说明</strong></td><td>与 §2.6 区别：不按会话筛选，影响整个收件箱。单条已读 <code>PUT /messages/{id}/read</code> 在 OpenAPI 中仍标记为废弃，优先用按会话已读或本接口。</td></tr>
        </tbody>
      </table>

      <h4>2.8 消息对象字段（MessageResponse，历史接口数组元素）</h4>
      <table class="param-table">
        <thead>
          <tr><th>字段</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><code>id</code></td><td>消息 ID。</td></tr>
          <tr><td><code>sender_id</code></td><td>发送方用户 ID；系统通知可能为 <code>null</code>。</td></tr>
          <tr><td><code>receiver_id</code></td><td>接收方用户 ID（当前登录用户）。</td></tr>
          <tr><td><code>type</code></td><td><code>MESSAGE</code> 或 <code>NOTIFICATION</code>。</td></tr>
          <tr><td><code>content_type</code></td><td><code>TEXT</code> / <code>IMAGE</code> / <code>VIDEO</code> / <code>FILE</code> / <code>USER_NOTIFICATION</code> 等。</td></tr>
          <tr><td><code>title</code> / <code>content</code></td><td>标题与正文；多媒体可能为预签名 URL。</td></tr>
          <tr><td><code>action_url</code> / <code>action_text</code></td><td>通知按钮；点击跳转前可配合 <code>GET /messages/{id}/callback-url</code>。</td></tr>
          <tr><td><code>is_read</code> / <code>read_at</code></td><td>是否已读及已读时间。</td></tr>
          <tr><td><code>app_id</code> / <code>app_name</code></td><td>应用维度信息（通知等场景）。</td></tr>
        </tbody>
      </table>

      <h4>2.9 WebSocket：连接与收到提醒后行为</h4>
      <ul>
        <li><strong>地址</strong>：<code>ws://&lt;主机&gt;/api/v1/ws/messages?token=&lt;JWT&gt;</code>（HTTPS 页面用 <code>wss://</code>）。</li>
        <li><strong>心跳</strong>：建议每 30 秒发送字符串 <code>ping</code>，收到 <code>pong</code>。</li>
        <li><strong>推送</strong>：JSON 中 <code>type === "NEW_MESSAGE"</code>，业务数据在 <code>data</code>。</li>
        <li><strong>收到推送后</strong>：触发 §2.3 + §2.4 刷新（或仅刷新会话列表），不要仅依赖推送内容渲染完整列表。</li>
      </ul>
    </div>

    <div class="section">
      <h3>3. 私信与通知接口</h3>
      <ul>
        <li><strong>发送入口</strong>：<code>POST /api/v1/messages/</code></li>
        <li><strong>私信</strong>：使用 <code>type: "MESSAGE"</code> + <code>receiver_id</code></li>
        <li><strong>通知</strong>：使用 <code>type: "NOTIFICATION"</code>（用户侧无权限发送系统通知）</li>
        <li><strong>按外部账号发送</strong>：<code>app_id + app_user_id</code> 组合解析接收者</li>
        <li><strong>用户申请通知</strong>：<code>type: "MESSAGE"</code> + <code>content_type: "USER_NOTIFICATION"</code>，在私信界面以通知卡片样式展示。可应用发（<code>app_id</code>+<code>app_user_id</code> 接收，可选 <code>sender_app_user_id</code>）或用户 Token 发（<code>receiver_id</code>），支持 <code>action_url</code> 按钮</li>
      </ul>

      <h4>发送接口调用说明（POST /api/v1/messages/）</h4>
      <table class="param-table">
        <thead>
          <tr><th>项目</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><strong>URL</strong></td><td><code>POST /api/v1/messages/</code>，<code>Content-Type: application/json</code></td></tr>
          <tr><td><strong>用户认证</strong></td><td><code>Authorization: Bearer &lt;JWT_TOKEN&gt;</code>；仅可发 <code>type: "MESSAGE"</code>，不可发 <code>NOTIFICATION</code></td></tr>
          <tr><td><strong>应用认证</strong></td><td><code>X-App-Id</code>（字符串 app_id）、<code>X-Timestamp</code>（Unix 秒）、<code>X-Sign</code>；签名：<code>HMAC-SHA256(app_secret, "app_id=...&amp;timestamp=...")</code>，键名按字母序拼接</td></tr>
        </tbody>
      </table>
      <table class="param-table" style="margin-top: 12px;">
        <thead>
          <tr><th>请求体字段</th><th>必填</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><code>type</code></td><td>是</td><td><code>MESSAGE</code> / <code>NOTIFICATION</code></td></tr>
          <tr><td><code>title</code></td><td>是</td><td>标题，最长 255</td></tr>
          <tr><td><code>content</code></td><td>是</td><td>文本字符串或 JSON 对象（对象会序列化为合法 JSON 字符串存储）</td></tr>
          <tr><td><code>content_type</code></td><td>否</td><td>默认 <code>TEXT</code>；可选 <code>IMAGE</code> / <code>VIDEO</code> / <code>FILE</code> / <code>USER_NOTIFICATION</code></td></tr>
          <tr><td><code>receiver_id</code></td><td>条件</td><td>统一平台用户 ID；与 <code>app_id</code>+<code>app_user_id</code> 二选一（广播时不要填）</td></tr>
          <tr><td><code>app_id</code></td><td>条件</td><td>应用字符串 ID；与 <code>app_user_id</code> 一起解析接收者；用户发 <code>USER_NOTIFICATION</code> 且 <code>auto_sso</code> 时需填以生成 jump</td></tr>
          <tr><td><code>app_user_id</code></td><td>条件</td><td>业务侧账号，须在 <code>app_user_mappings</code> 有映射</td></tr>
          <tr><td><code>sender_app_user_id</code></td><td>否</td><td>应用发信时：发起人业务账号，解析后写入 <code>sender_id</code> 用于审计和会话聚合；用户 JWT 发信忽略</td></tr>
          <tr><td><code>is_broadcast</code></td><td>否</td><td><code>true</code> 时对当前 <code>X-App-Id</code> 下已有有效映射的活跃用户发通知；仅 <code>type: NOTIFICATION</code> + 应用签名；勿填接收者</td></tr>
          <tr><td><code>auto_sso</code> / <code>target_url</code></td><td>否</td><td>为 <code>true</code> 且提供 <code>target_url</code> 时，<code>NOTIFICATION</code> 或 <code>USER_NOTIFICATION</code> 可自动生成 <code>action_url</code>（jump）</td></tr>
          <tr><td><code>action_url</code> / <code>action_text</code></td><td>否</td><td>自定义按钮链接与文案；若用 <code>callback-url</code> 接口，jump 格式需与后端约定一致</td></tr>
        </tbody>
      </table>
      <ul style="margin-top: 12px;">
        <li><strong>接收者</strong>：非广播时须满足其一——<code>receiver_id</code>，或同时 <code>app_id</code> + <code>app_user_id</code>。</li>
        <li><strong>响应</strong>：<code>201</code> 返回消息体（含 <code>id</code>、<code>action_url</code> 等）；失败常见 <code>403</code>（权限）、<code>404</code>（用户/映射不存在）、<code>422</code>（参数校验）。</li>
      </ul>

      <h4>示例：发送私信（用户 Token）</h4>
      <div class="code-block">
        <pre>
POST /api/v1/messages/ HTTP/1.1
Authorization: Bearer &lt;JWT_TOKEN&gt;
Content-Type: application/json

{
  "receiver_id": 2048,
  "type": "MESSAGE",
  "content_type": "TEXT",
  "title": "私信",
  "content": "你好，这是一条私信"
}</pre>
      </div>

      <h4>示例：发送应用通知（应用签名）</h4>
      <div class="code-block">
        <pre>
POST /api/v1/messages/ HTTP/1.1
X-App-Id: your_app_id
X-Timestamp: 1700000000
X-Sign: your_hmac_sign
Content-Type: application/json

{
  "type": "NOTIFICATION",
  "content_type": "TEXT",
  "title": "审批通知",
  "content": "您有一条待审批任务",
  "auto_sso": true,
  "target_url": "https://biz.example.com/todo/123",
  "action_text": "立即处理"
}</pre>
      </div>

      <h4>示例：应用通知指定业务账号（app_user_id）</h4>
      <div class="code-block">
        <pre>
POST /api/v1/messages/ HTTP/1.1
X-App-Id: your_app_id_string
X-Timestamp: 1708848000
X-Sign: &lt;HMAC-SHA256 十六进制&gt;
Content-Type: application/json

{
  "app_id": "your_app_id_string",
  "app_user_id": "zhangsan_oa",
  "type": "NOTIFICATION",
  "content_type": "TEXT",
  "title": "审批通知",
  "content": "您有一条待审批任务",
  "auto_sso": true,
  "target_url": "https://biz.example.com/todo/123",
  "action_text": "立即处理"
}</pre>
      </div>
      <p class="note">应用签名且用 <code>app_user_id</code> 定位接收者时，Body 须同时带 <code>app_id</code>，且与 <code>X-App-Id</code> 一致，否则返回 403。</p>

      <h4>示例：应用发 USER_NOTIFICATION（应用签名，按 app_user_id 接收）</h4>
      <p class="intro" style="margin-top: 8px;">
        应用代发用户申请通知：如 OA 系统代「申请人张三」通知「经理李四」。接收者用 <code>app_id</code>+<code>app_user_id</code> 定位；可选 <code>sender_app_user_id</code> 标识发起人，便于审计和会话聚合。
      </p>
      <div class="code-block">
        <pre>
POST /api/v1/messages/ HTTP/1.1
X-App-Id: your_app_id
X-Timestamp: 1708848000
X-Sign: &lt;HMAC-SHA256&gt;
Content-Type: application/json

{
  "app_id": "oa_system",
  "app_user_id": "lisi_manager",
  "sender_app_user_id": "zhangsan_oa",
  "type": "MESSAGE",
  "content_type": "USER_NOTIFICATION",
  "title": "请假申请",
  "content": {"applyType":"LEAVE","days":2,"businessId":123},
  "auto_sso": true,
  "target_url": "https://biz.example.com/leave/123",
  "action_text": "去审批"
}</pre>
      </div>

      <h4>示例：用户发 USER_NOTIFICATION（用户 Token，重新提醒等）</h4>
      <p class="intro" style="margin-top: 8px;">
        用户本人发起：如申请人向经理发送提醒；<code>sender_id</code> 为当前用户，<code>receiver_id</code> 指定接收者。
      </p>
      <div class="code-block">
        <pre>
POST /api/v1/messages/ HTTP/1.1
Authorization: Bearer &lt;JWT_TOKEN&gt;
Content-Type: application/json

{
  "receiver_id": 2048,
  "type": "MESSAGE",
  "content_type": "USER_NOTIFICATION",
  "app_id": "oa_system",
  "title": "请假申请",
  "content": {"applyType":"LEAVE","days":2,"businessId":123},
  "auto_sso": true,
  "target_url": "https://biz.example.com/leave/123",
  "action_text": "去审批"
}</pre>
      </div>

      <h4>Python 调用示例（requests）</h4>
      <p class="intro">需安装：<code>pip install requests</code>。将 <code>API_BASE</code>、<code>APP_ID</code>、<code>APP_SECRET</code>、<code>ACCESS_TOKEN</code> 替换为实际值。</p>
      <div class="code-block">
        <pre>
import hmac
import hashlib
import time
import requests

API_BASE = "https://your-host.example.com/api/v1"  # 无尾斜杠
APP_ID = "your_app_id_string"
APP_SECRET = "your_app_secret"


def app_sign_headers() -&gt; dict:
    ts = str(int(time.time()))
    query_string = f"app_id={APP_ID}&amp;timestamp={ts}"
    sign = hmac.new(
        APP_SECRET.encode("utf-8"),
        query_string.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()
    return {
        "X-App-Id": APP_ID,
        "X-Timestamp": ts,
        "X-Sign": sign,
        "Content-Type": "application/json",
    }


# 1) 应用签名：按 app_user_id 发送通知
def send_notification_by_app_user():
    url = f"{API_BASE}/messages/"
    payload = {
        "app_id": APP_ID,
        "app_user_id": "zhangsan_oa",
        "type": "NOTIFICATION",
        "content_type": "TEXT",
        "title": "审批通知",
        "content": "您有一条待审批任务",
        "auto_sso": True,
        "target_url": "https://biz.example.com/todo/123",
        "action_text": "立即处理",
    }
    r = requests.post(url, headers=app_sign_headers(), json=payload, timeout=30)
    r.raise_for_status()
    return r.json()


# 2) 应用发 USER_NOTIFICATION（代发起人通知接收者）
def send_user_notification_by_app(app_user_id: str, sender_app_user_id: str = None):
    url = f"{API_BASE}/messages/"
    payload = {
        "app_id": APP_ID,
        "app_user_id": app_user_id,
        "type": "MESSAGE",
        "content_type": "USER_NOTIFICATION",
        "title": "请假申请",
        "content": {"applyType": "LEAVE", "days": 2, "businessId": 123},
        "auto_sso": True,
        "target_url": "https://biz.example.com/leave/123",
        "action_text": "去审批",
    }
    if sender_app_user_id:
        payload["sender_app_user_id"] = sender_app_user_id
    r = requests.post(url, headers=app_sign_headers(), json=payload, timeout=30)
    r.raise_for_status()
    return r.json()


# 3) 用户 Token：发送 USER_NOTIFICATION（用户本人发，如重新提醒）
def send_user_notification(access_token: str):
    url = f"{API_BASE}/messages/"
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    }
    payload = {
        "receiver_id": 2048,
        "type": "MESSAGE",
        "content_type": "USER_NOTIFICATION",
        "app_id": "oa_system",
        "title": "请假申请",
        "content": {"applyType": "LEAVE", "days": 2, "businessId": 123},
        "auto_sso": True,
        "target_url": "https://biz.example.com/leave/123",
        "action_text": "去审批",
    }
    r = requests.post(url, headers=headers, json=payload, timeout=30)
    r.raise_for_status()
    return r.json()


if __name__ == "__main__":
    print(send_notification_by_app_user())
    # print(send_user_notification_by_app("lisi_manager", "zhangsan_oa"))
    # print(send_user_notification("eyJhbGciOiJIUzI1NiIsInR5cCI6..."))</pre>
      </div>
    </div>

    <div class="section">
      <h3>4. 通知回调接口</h3>
      <ul>
        <li><strong>接口</strong>：<code>GET /api/v1/messages/{message_id}/callback-url</code></li>
        <li><strong>用途</strong>：点击通知按钮时，实时生成带 ticket 的可跳转 URL</li>
        <li><strong>权限</strong>：仅消息接收者可调用</li>
      </ul>

      <h4>示例：获取 callback URL（点击通知按钮时调用）</h4>
      <div class="code-block">
        <pre>
GET /api/v1/messages/501/callback-url HTTP/1.1
Authorization: Bearer &lt;JWT_TOKEN&gt;</pre>
      </div>
      <div class="code-block">
        <pre>
{
  "callback_url": "https://app.example.com/callback?ticket=abc123&amp;next=https://biz.example.com/todo/123"
}</pre>
      </div>
    </div>

    <div class="section">
      <h3>5. 对话列表与未读（索引）</h3>
      <p>会话列表、全局未读、聊天历史、按会话已读、收件箱全部已读的<strong>完整请求参数、响应字段与推荐调用顺序</strong>见 <strong>第 2 节</strong>。本节仅作导航：聚合规则为私信按用户、系统通知按应用拆分会话；列表项中的 <code>user_id</code> 即后续 <code>history/{other_user_id}</code> 的路径参数。</p>
    </div>

    <div class="section">
      <h3>6. WebSocket 通信接口详情</h3>
      <ul>
        <li><strong>连接地址</strong>：<code>/api/v1/ws/messages?token=JWT_TOKEN</code></li>
        <li><strong>心跳</strong>：客户端每 30 秒发送 <code>ping</code>，服务端回复 <code>pong</code></li>
        <li><strong>消息类型</strong>：服务端推送 <code>NEW_MESSAGE</code></li>
        <li><strong>格式与归类</strong>：WS 推送体包含 <code>data.type</code> / <code>data.app_id</code>，用于区分私信与应用通知；收到后请按第 2 节刷新 HTTP 列表。</li>
        <li><strong>重连建议</strong>：指数退避重连，并在重连成功后补拉会话和历史</li>
      </ul>

      <h4>示例：建立连接与心跳（浏览器）</h4>
      <div class="code-block">
        <pre>
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const wsUrl = `${protocol}//${window.location.host}/api/v1/ws/messages?token=${encodeURIComponent(token)}`
const ws = new WebSocket(wsUrl)

ws.onopen = () =&gt; {
  // 每 30 秒 ping 一次
  setInterval(() =&gt; {
    if (ws.readyState === WebSocket.OPEN) ws.send('ping')
  }, 30000)
}

ws.onmessage = (event) =&gt; {
  if (event.data === 'pong') return
  const msg = JSON.parse(event.data)
  if (msg.type === 'NEW_MESSAGE') {
    // msg.data 即消息体
  }
}</pre>
      </div>

      <h4>示例：客户端收到的推送（区分私信 / 应用通知）</h4>
      <div class="code-block">
        <pre>
// 私信（MESSAGE）
{
  "type": "NEW_MESSAGE",
  "data": {
    "id": 1024,
    "type": "MESSAGE",
    "content_type": "TEXT",
    "title": "私信",
    "content": "你好",
    "sender_id": 2048,
    "sender_name": "用户私信",
    "created_at": "2026-03-18T10:10:00",
    "app_id": null,
    "app_name": null
  }
}

// 应用通知（NOTIFICATION）
{
  "type": "NEW_MESSAGE",
  "data": {
    "id": 20480,
    "type": "NOTIFICATION",
    "content_type": "TEXT",
    "title": "审批通知",
    "content": "您有一条待审批任务",
    "sender_id": null,
    "sender_name": "系统通知",
    "created_at": "2026-03-18T10:12:00",
    "action_url": "/api/v1/simple/sso/jump?app_id=oa_system&amp;redirect_to=https%3A%2F%2Fbiz.example.com%2Ftodo%2F123",
    "action_text": "立即处理",
    "app_id": 101,
    "app_name": "OA系统"
  }
}</pre>
      </div>
    </div>

    <div class="section">
      <h3>7. 联系人查询接口</h3>
      <ul>
        <li><strong>当前用户</strong>：<code>GET /api/v1/users/me</code>（含 <code>organization_id</code>、<code>organization_name</code>，未归属组织时二者为 <code>null</code>；无嵌套组织对象）</li>
        <li><strong>推荐接口</strong>：<code>GET /api/v1/users/search?q=关键词&amp;limit=20</code></li>
        <li><strong>搜索字段</strong>：手机号、姓名、英文名</li>
        <li><strong>管理分页接口</strong>：<code>GET /api/v1/users/?skip=0&amp;limit=20&amp;keyword=xxx</code></li>
      </ul>

      <h4>示例：获取当前登录用户</h4>
      <div class="code-block">
        <pre>
GET /api/v1/users/me HTTP/1.1
Authorization: Bearer &lt;JWT_TOKEN&gt;</pre>
      </div>
      <div class="code-block">
        <pre>
{
  "id": 2048,
  "mobile": "13800138000",
  "name": "张三",
  "english_name": "zhangsan",
  "organization_id": 10,
  "organization_name": "某某部门",
  "status": "ACTIVE",
  "role": "ORDINARY_USER",
  "is_deleted": 0,
  "created_at": "2026-01-01T12:00:00+08:00",
  "updated_at": "2026-04-01T10:00:00+08:00"
}</pre>
      </div>

      <h4>示例：搜索联系人（推荐）</h4>
      <div class="code-block">
        <pre>
GET /api/v1/users/search?q=张三&amp;limit=20 HTTP/1.1
Authorization: Bearer &lt;JWT_TOKEN&gt;</pre>
      </div>
      <div class="code-block">
        <pre>
[
  {
    "id": 2048,
    "mobile": "13800138000",
    "name": "张三",
    "english_name": "zhangsan",
    "status": "ACTIVE"
  }
]</pre>
      </div>

      <h4>示例：用户列表分页（管理/全量检索场景）</h4>
      <div class="code-block">
        <pre>
GET /api/v1/users/?skip=0&amp;limit=20&amp;keyword=张三 HTTP/1.1
Authorization: Bearer &lt;JWT_TOKEN&gt;</pre>
      </div>
      <div class="code-block">
        <pre>
{
  "total": 100,
  "items": [
    {
      "id": 2048,
      "mobile": "13800138000",
      "name": "张三",
      "english_name": "zhangsan",
      "status": "ACTIVE"
    }
  ]
}</pre>
      </div>
    </div>

    <div class="section">
      <h3>8. 应用中心（快捷导航）与平台账号管理：可访问应用查询</h3>
      <p class="intro">
        本组接口用于<strong>快捷导航</strong>与<strong>平台账号管理</strong>：先拉取当前用户<strong>可访问的应用列表</strong>（已建立有效账号映射、协议为 <code>SIMPLE_API</code>、<code>OIDC</code> 或 <code>NONE</code>（导航）、映射已启用且应用未删除），再按需调用 <code>sso-login</code> 获取跳转地址。<strong>无 Query 分页</strong>，<code>total</code> 与 <code>items.length</code> 一致。
      </p>
      <p class="intro">
        若需<strong>含已停用映射</strong>或按应用名<strong>分页筛选</strong>的完整映射清单，请用 <code>GET /api/v1/simple/me/mappings</code>（见《平台账号管理接口文档》帮助页）。若仅需「当前可展示/可跳转」的应用，以本接口为准。按关键字检索后台应用请用 <code>GET /api/v1/apps/?search=</code>（多为开发者/管理员权限，与普通终端用户场景不同）。
      </p>

      <h4>8.1 查询当前可访问的应用列表</h4>
      <table class="param-table">
        <thead>
          <tr><th>项目</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><strong>方法 / 路径</strong></td><td><code>GET /api/v1/simple/me/launchpad-apps</code></td></tr>
          <tr><td><strong>请求头</strong></td><td><code>Authorization: Bearer &lt;JWT_TOKEN&gt;</code></td></tr>
          <tr><td><strong>查询参数</strong></td><td>无</td></tr>
          <tr><td><strong>请求体</strong></td><td>无</td></tr>
          <tr><td><strong>成功响应</strong></td><td><code>200</code>，JSON 对象，字段见下表</td></tr>
        </tbody>
      </table>

      <p><strong>响应字段</strong></p>
      <table class="param-table">
        <thead>
          <tr><th>字段</th><th>类型</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><code>total</code></td><td>number</td><td>列表条数，等于 <code>items</code> 长度</td></tr>
          <tr><td><code>items</code></td><td>array</td><td>可访问应用条目</td></tr>
          <tr><td><code>items[].app_name</code></td><td>string</td><td>应用显示名称</td></tr>
          <tr><td><code>items[].app_id</code></td><td>string</td><td>业务侧应用 ID（与 SSO 等接口入参一致）</td></tr>
          <tr><td><code>items[].protocol_type</code></td><td>string</td><td>如 <code>SIMPLE_API</code>、<code>OIDC</code>、<code>NONE</code>（仅导航）</td></tr>
          <tr><td><code>items[].mapped_key</code></td><td>string | null</td><td>该应用下的映射账号</td></tr>
          <tr><td><code>items[].mapped_email</code></td><td>string | null</td><td>该应用下的映射邮箱</td></tr>
          <tr><td><code>items[].is_active</code></td><td>boolean</td><td>映射是否启用；本列表内一般为 <code>true</code></td></tr>
          <tr><td><code>items[].description</code></td><td>string | null</td><td>应用描述</td></tr>
          <tr><td><code>items[].category_id</code></td><td>number | null</td><td>分类 ID</td></tr>
          <tr><td><code>items[].category_name</code></td><td>string | null</td><td>分类名称</td></tr>
          <tr><td><code>items[].icon_url</code></td><td>string | null</td><td>Logo 展示用地址：<strong>对象存储场景下</strong>为<strong>短时预签名 GET</strong> URL（签名与过期参数会轮换）；若库内写的是完整 HTTP(S) 直链则与原值一致。</td></tr>
          <tr><td><code>items[].icon_object_key</code></td><td>string | null</td><td>当 Logo 存于平台对象存储时为<strong>桶内对象路径</strong>（稳定，不随签名变化）；直链或未配置 Logo 时为 <code>null</code>。自建 Web/移动端可用 <code>app_id</code> + <code>icon_object_key</code> 作本地缓存键，用当期 <code>icon_url</code> 拉取二进制；Logo 替换后后端会换新路径，缓存键也随之更新。</td></tr>
        </tbody>
      </table>

      <p><strong>错误</strong>：<code>401</code> 未携带或 Token 无效（如 <code>{"detail":"Not authenticated"}</code>，以服务端为准）。</p>

      <h4>示例：HTTP 请求</h4>
      <div class="code-block">
        <pre>
GET /api/v1/simple/me/launchpad-apps HTTP/1.1
Host: your-host.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Accept: application/json</pre>
      </div>
      <div class="code-block">
        <pre>
curl -sS -X GET "https://your-host.example.com/api/v1/simple/me/launchpad-apps" \
  -H "Authorization: Bearer &lt;JWT_TOKEN&gt;" \
  -H "Accept: application/json"</pre>
      </div>

      <h4>示例：成功响应 JSON</h4>
      <div class="code-block">
        <pre>
{
  "total": 3,
  "items": [
    {
      "app_name": "OA系统",
      "app_id": "oa_system",
      "protocol_type": "SIMPLE_API",
      "mapped_key": "zhangsan",
      "mapped_email": "zhangsan@corp.com",
      "is_active": true,
      "description": "办公自动化系统",
      "category_id": 1,
      "category_name": "办公协同",
      "icon_url": "https://files.example.com/presigned/app_icon/oa.png?X-Amz-Expires=3600",
      "icon_object_key": "app_icon/42/a1b2c3d4-e5f6-7890-abcd-ef1234567890.png"
    },
    {
      "app_name": "CRM客户管理",
      "app_id": "crm_system",
      "protocol_type": "OIDC",
      "mapped_key": "zhangsan_crm",
      "mapped_email": null,
      "is_active": true,
      "description": null,
      "category_id": null,
      "category_name": null,
      "icon_url": null,
      "icon_object_key": null
    },
    {
      "app_name": "外部导航",
      "app_id": "nav_site",
      "protocol_type": "NONE",
      "mapped_key": "zhangsan",
      "mapped_email": null,
      "is_active": true,
      "description": null,
      "category_id": null,
      "category_name": null,
      "icon_url": null,
      "icon_object_key": null
    }
  ]
}</pre>
      </div>

      <h4>8.2 点击应用进入（SSO 登录获取 redirect_url）</h4>
      <p class="intro">
        与 <code>POST /api/v1/simple/sso-login</code> 约定：<code>SIMPLE_API</code> 返回带 <code>ticket</code> 的回调 URL；<code>OIDC</code> 返回 <code>scheme://host</code>（已登录场景下的跳转入口）；<code>NONE</code> 返回应用配置的导航 URL 原文，<strong>不含 ticket</strong>。
      </p>
      <div class="code-block">
        <pre>
POST /api/v1/simple/sso-login HTTP/1.1
Authorization: Bearer &lt;JWT_TOKEN&gt;
Content-Type: application/json

{
  "app_id": "oa_system",
  "username": "",
  "password": ""
}</pre>
      </div>
      <div class="code-block">
        <pre>
{
  "redirect_url": "https://oa.example.com/callback?ticket=abc123"
}</pre>
      </div>
    </div>

    <div class="section">
      <h3>9. 短信验证码重置密码（开放接口）</h3>
      <p>
        适用于<strong>忘记旧密码</strong>、无法提供旧密码的场景。流程为<strong>图形验证码 → 短信验证码 → 新密码</strong>，<strong>无需</strong>
        <code>Authorization</code>。
        已登录且知道旧密码时，请使用 <code>POST /api/v1/simple/me/change-password</code>（需 Bearer Token）。
      </p>

      <h4>9.1 调用顺序</h4>
      <ol>
        <li><code>GET /api/v1/utils/captcha</code> — 取 <code>captcha_id</code> 与图片</li>
        <li><code>POST /api/v1/open/sms/send</code> — 校验图形码后发短信</li>
        <li><code>POST /api/v1/open/pwd/reset</code> — 校验短信后更新密码</li>
      </ol>

      <h4>9.2 <code>GET /utils/captcha</code></h4>
      <table class="param-table">
        <thead>
          <tr><th>项目</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><strong>方法 / 路径</strong></td><td><code>GET /api/v1/utils/captcha</code></td></tr>
          <tr><td><strong>认证</strong></td><td>无</td></tr>
          <tr>
            <td><strong>成功响应</strong></td>
            <td><code>200</code>，JSON：<code>captcha_id</code>、<code>image</code>（Base64 图）、<code>expire_seconds</code></td>
          </tr>
        </tbody>
      </table>

      <h4>9.3 <code>POST /open/sms/send</code></h4>
      <table class="param-table">
        <thead>
          <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><code>mobile</code></td><td>string</td><td>是</td><td>已注册手机号</td></tr>
          <tr><td><code>captcha_id</code></td><td>string</td><td>是</td><td>来自 captcha 接口</td></tr>
          <tr><td><code>captcha_code</code></td><td>string</td><td>是</td><td>用户输入的图形验证码</td></tr>
        </tbody>
      </table>
      <p>成功：<code>200</code>，<code>{"message": "短信发送成功"}</code>。图形码错误时常见 <code>400</code>：<code>图形验证码无效</code>。</p>

      <h4>9.4 <code>POST /open/pwd/reset</code></h4>
      <table class="param-table">
        <thead>
          <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><code>mobile</code></td><td>string</td><td>是</td><td>与发短信时一致</td></tr>
          <tr><td><code>sms_code</code></td><td>string</td><td>是</td><td>短信验证码</td></tr>
          <tr><td><code>new_password</code></td><td>string</td><td>是</td><td>新密码（须含字母与数字）</td></tr>
        </tbody>
      </table>
      <p>
        成功：<code>200</code>，<code>{"message": "密码重置成功"}</code>。
        常见错误：<code>400</code> 短信无效、密码强度不足；<code>404</code> 用户未找到（手机号未注册）。
      </p>

      <h4>9.5 请求示例（节选）</h4>
      <div class="code-block">
        <pre>
POST /api/v1/open/sms/send HTTP/1.1
Host: your-domain.example.com
Content-Type: application/json

{
  "mobile": "13800138000",
  "captcha_id": "&lt;来自 captcha 响应&gt;",
  "captcha_code": "abcd"
}</pre>
      </div>
      <div class="code-block">
        <pre>
POST /api/v1/open/pwd/reset HTTP/1.1
Host: your-domain.example.com
Content-Type: application/json

{
  "mobile": "13800138000",
  "sms_code": "123456",
  "new_password": "newSecret1"
}</pre>
      </div>
    </div>

    <div class="section">
      <h3>10. 个人身份二维码</h3>
      <p>
        用于「当前登录用户生成加密身份令牌 → 前端将令牌绘制成二维码 → 核验端扫码后调用接口换明文身份」。
        令牌为 <strong>AES-256-GCM</strong> 加密后的 <strong>Base64 URL-safe</strong> 字符串，<strong>短时效</strong>（默认约 1 分钟，以服务端 <code>IDENTITY_QR_VALID_SECONDS</code> 为准）。
      </p>

      <h4>10.1 获取二维码密文（展示端）</h4>
      <table class="param-table">
        <thead>
          <tr><th>项目</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><strong>方法 / 路径</strong></td><td><code>GET /api/v1/identity-qr/</code></td></tr>
          <tr><td><strong>认证</strong></td><td><code>Authorization: Bearer &lt;JWT_TOKEN&gt;</code>（需为已激活用户）</td></tr>
        </tbody>
      </table>
      <p>成功：<code>200</code>，JSON：</p>
      <table class="param-table">
        <thead>
          <tr><th>字段</th><th>类型</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><code>token</code></td><td>string</td><td>AES-GCM 密文，<strong>直接作为二维码内容</strong>；客户端不要尝试解密</td></tr>
          <tr><td><code>expires_at</code></td><td>string（ISO8601，UTC）</td><td>过期时间，可用于展示剩余有效时间</td></tr>
        </tbody>
      </table>
      <div class="code-block">
        <pre>
GET /api/v1/identity-qr/ HTTP/1.1
Authorization: Bearer &lt;JWT_TOKEN&gt;</pre>
      </div>
      <div class="code-block">
        <pre>
{
  "token": "&lt;AES-GCM 密文 Base64 URL-safe&gt;",
  "expires_at": "2026-04-01T12:34:56.789000Z"
}</pre>
      </div>

      <h4>10.2 核验扫码结果（核验端）</h4>
      <table class="param-table">
        <thead>
          <tr><th>项目</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><strong>方法 / 路径</strong></td><td><code>POST /api/v1/identity-qr/verify</code></td></tr>
          <tr><td><strong>Content-Type</strong></td><td><code>application/json</code></td></tr>
        </tbody>
      </table>
      <p><strong>认证（二选一）</strong></p>
      <ul>
        <li><strong>用户</strong>：<code>Authorization: Bearer &lt;JWT_TOKEN&gt;</code></li>
        <li><strong>业务应用</strong>：<code>X-App-Access-Token: &lt;应用 Access Token&gt;</code>（与平台为该应用分配的 <code>access_token</code> 一致）</li>
      </ul>
      <table class="param-table">
        <thead>
          <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><code>token</code></td><td>string</td><td>是</td><td>扫码得到的完整字符串（与 10.1 获取接口返回的 <code>token</code> 一致）</td></tr>
        </tbody>
      </table>
      <p>成功：<code>200</code>，JSON：</p>
      <table class="param-table">
        <thead>
          <tr><th>字段</th><th>类型</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><code>id</code></td><td>number</td><td>用户 ID</td></tr>
          <tr><td><code>name</code></td><td>string | null</td><td>姓名</td></tr>
          <tr><td><code>mobile</code></td><td>string</td><td>手机号</td></tr>
        </tbody>
      </table>
      <div class="code-block">
        <pre>
POST /api/v1/identity-qr/verify HTTP/1.1
Authorization: Bearer &lt;JWT_TOKEN&gt;
Content-Type: application/json

{
  "token": "&lt;扫码得到的完整字符串&gt;"
}</pre>
      </div>
      <div class="code-block">
        <pre>
{
  "id": 12345,
  "name": "张三",
  "mobile": "13800138000"
}</pre>
      </div>

      <h4>10.3 错误与集成注意</h4>
      <table class="param-table">
        <thead>
          <tr><th>HTTP</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><code>401</code></td><td>未提供有效 JWT 或 <code>X-App-Access-Token</code></td></tr>
          <tr><td><code>400</code></td><td>令牌无法解密、已过期、载荷无效、用户非激活、或 token 内手机号与库中不一致等（<code>detail</code> 为文本）</td></tr>
          <tr><td><code>404</code></td><td>对应用户不存在或已删除</td></tr>
        </tbody>
      </table>
      <p class="note">令牌有效期较短，生成后应尽快展示并完成核验；过期后需用户重新打开「我的二维码」以获取新 <code>token</code>。</p>
    </div>
  </div>
</template>

<script setup lang="ts">
import { Download } from '@element-plus/icons-vue'
import { useHelpDocs } from '../../composables/useHelpDocs'

const { downloadDoc } = useHelpDocs()
</script>

<style scoped>
@import './help.css';
</style>
