瀏覽代碼

更新接口文档

liuq 1 月之前
父節點
當前提交
4186ac4cb9

+ 8 - 4
backend/app/api/v1/endpoints/ws.py

@@ -5,7 +5,7 @@ from app.core.config import settings
 from jose import jwt, JWTError
 from app.api.v1 import deps
 from sqlalchemy.orm import Session
-from app.core.database import get_db
+from app.core.database import get_db, SessionLocal
 from app.models.user import User
 
 router = APIRouter()
@@ -23,14 +23,18 @@ async def get_user_from_token(token: str, db: Session):
 @router.websocket("/messages")
 async def websocket_endpoint(
     websocket: WebSocket,
-    token: str = Query(...),
-    db: Session = Depends(get_db)
+    token: str = Query(...)
 ):
     """
     全平台通用的 WebSocket 连接端点
     连接 URL: ws://host/api/v1/ws/messages?token=YOUR_JWT_TOKEN
     """
-    user = await get_user_from_token(token, db)
+    db = SessionLocal()
+    try:
+        user = await get_user_from_token(token, db)
+    finally:
+        db.close()
+
     if not user:
         await websocket.close(code=4001, reason="Authentication failed")
         return

+ 3 - 2
backend/app/core/database.py

@@ -6,7 +6,9 @@ from app.core.config import settings
 engine = create_engine(
     settings.DATABASE_URI,
     pool_pre_ping=True,
-    echo=False
+    echo=False,
+    pool_size=20,       # 增加连接池大小
+    max_overflow=40     # 允许溢出更多连接
 )
 
 SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
@@ -19,4 +21,3 @@ def get_db():
         yield db
     finally:
         db.close()
-

+ 76 - 6
docs/api_message_system_v1.md

@@ -145,7 +145,77 @@ erDiagram
 
 **Base URL**: `/api/v1`
 
-### 3.1 消息发送 (Send Message)
+### 3.1 用户登录集成 (User Login)
+
+统一认证平台提供基于密码和短信验证码的标准登录接口(Auth 模块)。
+
+#### 3.1.1 密码登录 (Password Login)
+
+提供两种方式:标准 OAuth2 表单方式和 JSON 方式。
+
+**方式一:OAuth2 表单登录**
+
+*   **Endpoint**: `POST /auth/login`
+*   **Content-Type**: `application/x-www-form-urlencoded`
+*   **Request Body**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `username` | string | 是 | 手机号 (字段名固定为 username) |
+| `password` | string | 是 | 密码 |
+
+*   **Response**:
+```json
+{
+  "access_token": "eyJhbGciOiJIUzI1NiIsInR...",
+  "token_type": "bearer"
+}
+```
+
+**方式二:JSON 登录**
+
+*   **Endpoint**: `POST /auth/login/json`
+*   **Content-Type**: `application/json`
+*   **Request Body**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `mobile` | string | 是 | 手机号 |
+| `password` | string | 是 | 密码 |
+| `remember_me`| bool | 否 | 是否记住登录 (默认 False) |
+
+*   **Response**: 同上。
+
+#### 3.1.2 短信验证码登录 (SMS Login)
+
+需要先调用发送验证码接口,再调用登录接口。
+
+**第一步:发送验证码**
+
+*   **Endpoint**: `POST /auth/sms/send-code`
+*   **Request Body**:
+```json
+{
+  "mobile": "13800138000",
+  "platform": "pc"
+}
+```
+> `platform` 可选值: `pc`, `mobile` (需后台开启对应配置)
+
+**第二步:验证码登录**
+
+*   **Endpoint**: `POST /auth/sms/login`
+*   **Request Body**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `mobile` | string | 是 | 手机号 |
+| `code` | string | 是 | 验证码 |
+| `platform` | string | 否 | 平台类型 (默认 pc) |
+
+*   **Response**: 同密码登录。
+
+### 3.2 消息发送 (Send Message)
 
 支持用户私信和应用通知的统一发送接口。
 
@@ -193,7 +263,7 @@ erDiagram
 }
 ```
 
-### 3.2 获取会话列表 (Get Conversations)
+### 3.3 获取会话列表 (Get Conversations)
 
 获取当前用户的会话聚合列表(类似微信首页)。
 
@@ -224,7 +294,7 @@ erDiagram
 ]
 ```
 
-### 3.3 获取聊天记录 (Get History)
+### 3.4 获取聊天记录 (Get History)
 
 *   **Endpoint**: `GET /messages/history/{other_user_id}`
 *   **Params**:
@@ -233,14 +303,14 @@ erDiagram
     *   `limit`: 条数 (默认50)
 *   **Response**: List of Messages
 
-### 3.4 消息状态管理
+### 3.5 消息状态管理
 
 *   **获取未读数**: `GET /messages/unread-count`
 *   **标记单条已读**: `PUT /messages/{message_id}/read`
 *   **标记全部已读**: `PUT /messages/read-all`
 *   **删除消息**: `DELETE /messages/{message_id}`
 
-### 3.5 文件上传 (Upload)
+### 3.6 文件上传 (Upload)
 
 *   **Endpoint**: `POST /messages/upload`
 *   **Content-Type**: `multipart/form-data`
@@ -256,7 +326,7 @@ erDiagram
 ```
 > **注意**: 返回的 `url` 是 MinIO 对象 Key,发送消息时应将此 JSON 作为 `content` 发送。
 
-### 3.6 SSO 跳转 (SSO Jump)
+### 3.7 SSO 跳转 (SSO Jump)
 
 *   **Endpoint**: `GET /auth/sso/jump`
 *   **Params**:

+ 303 - 0
frontend/public/docs/api_message_system_v1.md

@@ -0,0 +1,303 @@
+# 统一消息系统数据库与接口文档
+
+**版本**: V1.1
+**日期**: 2026-02-23
+**状态**: 已发布
+
+本文档详细描述了统一消息平台的数据库设计(Schema)、API 接口规范以及权限控制策略。
+
+---
+
+## 1. 数据库设计 (Database Schema)
+
+### 1.1 ER 图 (Entity-Relationship Diagram)
+
+```mermaid
+erDiagram
+    users ||--o{ messages : "receives"
+    users ||--o{ messages : "sends"
+    users ||--o{ user_devices : "has"
+    applications ||--o{ messages : "sends_notifications"
+    applications ||--o{ app_user_mapping : "maps_users"
+    users ||--o{ app_user_mapping : "mapped_by"
+
+    users {
+        int id PK
+        string username
+        string mobile
+    }
+
+    applications {
+        int id PK
+        string app_name
+        string app_secret
+    }
+    
+    app_user_mapping {
+        int id PK
+        int app_id FK
+        int user_id FK
+        string app_user_id "User ID in 3rd party app"
+    }
+
+    messages {
+        bigint id PK
+        int sender_id FK "Nullable"
+        int receiver_id FK
+        int app_id FK "Nullable"
+        enum type "MESSAGE|NOTIFICATION"
+        enum content_type "TEXT|IMAGE|VIDEO|FILE"
+        string title
+        text content "JSON or String"
+        string action_url "SSO Link"
+        bool is_read
+        datetime created_at
+    }
+
+    user_devices {
+        int id PK
+        int user_id FK
+        string device_token "Unique per User"
+        string platform "ios|android|harmony"
+        datetime last_active
+    }
+```
+
+### 1.2 表结构详解
+
+#### 1.2.1 消息表 (`messages`)
+
+存储所有用户私信和系统通知的核心表。
+
+| 字段名 | 类型 | 必填 | 默认值 | 说明 |
+| :--- | :--- | :--- | :--- | :--- |
+| `id` | BIGINT | 是 | Auto Inc | 主键,消息唯一标识 |
+| `sender_id` | INT | 否 | NULL | 发送者 UserID (私信必填,系统通知为空) |
+| `receiver_id` | INT | 是 | - | **接收者 UserID** (关联 `users.id`) |
+| `app_id` | INT | 否 | NULL | 来源应用 ID (关联 `applications.id`) |
+| `type` | VARCHAR(20) | 是 | MESSAGE | 消息类型: `MESSAGE` (私信), `NOTIFICATION` (通知) |
+| `content_type` | VARCHAR(20) | 是 | TEXT | 内容类型: `TEXT`, `IMAGE`, `VIDEO`, `FILE` |
+| `title` | VARCHAR(255) | 是 | - | 消息标题 (私信可与内容一致或摘要) |
+| `content` | TEXT | 是 | - | 消息内容。多媒体类型存储 JSON 字符串 (如 `{"url":"...", "size":1024}`) |
+| `action_url` | VARCHAR(1000)| 否 | NULL | 点击跳转链接 (通常包含 SSO Ticket) |
+| `action_text` | VARCHAR(50) | 否 | NULL | 跳转按钮文案 (如"去审批") |
+| `is_read` | TINYINT(1) | 是 | 0 | 是否已读 (0:未读, 1:已读) |
+| `created_at` | TIMESTAMP | 是 | NOW() | 创建时间 |
+| `read_at` | TIMESTAMP | 否 | NULL | 阅读时间 |
+
+**索引**:
+- `idx_receiver_id`: `(receiver_id)` - 加速“我的消息”查询
+- `idx_sender_id`: `(sender_id)` - 加速“我发送的消息”查询
+- `idx_app_id`: `(app_id)` - 统计应用发送量
+
+#### 1.2.2 用户设备表 (`user_devices`)
+
+存储移动端设备的推送 Token,用于离线推送。
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `id` | INT | 是 | 主键 |
+| `user_id` | INT | 是 | 关联用户 ID |
+| `device_token` | VARCHAR(255)| 是 | 厂商推送 Token (如 APNs Token) |
+| `platform` | VARCHAR(20) | 是 | 平台: `ios`, `android`, `harmony` |
+| `device_name` | VARCHAR(100)| 否 | 设备名称 (如 "iPhone 13") |
+| `last_active` | TIMESTAMP | 是 | 最后活跃时间 |
+
+**约束**:
+- `uq_user_device`: `UNIQUE(user_id, device_token)` - 防止同一用户同一设备重复注册
+
+---
+
+## 2. 接口权限设计 (Authentication & Authorization)
+
+本系统采用 **混合认证策略 (Hybrid Auth)**,同时支持用户操作和第三方系统调用。
+
+### 2.1 认证方式
+
+#### A. 用户认证 (User Context)
+*   **适用场景**: 移动端/Web端用户发送私信、查询历史消息。
+*   **机制**: HTTP Header `Authorization: Bearer <JWT_TOKEN>`
+*   **身份**: 解析 JWT 获取 `current_user` 对象。
+
+#### B. 应用认证 (Application Context)
+*   **适用场景**: OA/ERP 等后端系统调用接口发送业务通知。
+*   **机制**: HTTP Headers 签名验证。
+    *   `X-App-Id`: 应用 ID
+    *   `X-Timestamp`: 当前时间戳 (用于防重放)
+    *   `X-Sign`: 签名字符串
+*   **签名算法**: `MD5(app_id + timestamp + app_secret)`
+*   **身份**: 验证通过后获取 `current_app` 对象。
+
+### 2.2 权限控制矩阵
+
+| 操作 | 接口路径 | 用户 (User) 权限 | 应用 (App) 权限 | 说明 |
+| :--- | :--- | :--- | :--- | :--- |
+| **发送私信** | `POST /messages/` | ✅ 允许 (type=MESSAGE) | ✅ 允许 | 应用可冒充系统发私信 |
+| **发送通知** | `POST /messages/` | ❌ 禁止 | ✅ 允许 (type=NOTIFICATION) | 只有应用能发通知 |
+| **查询列表** | `GET /messages/` | ✅ 仅限查询自己的 | ❌ 禁止 | 应用只负责发,不负责查 |
+| **会话列表** | `GET /conversations` | ✅ 仅限查询自己的 | ❌ 禁止 | - |
+| **标记已读** | `PUT /read` | ✅ 仅限操作自己的 | ❌ 禁止 | 状态由用户端触发 |
+| **上传文件** | `POST /upload` | ✅ 允许 | ✅ 允许 | - |
+
+---
+
+## 3. API 接口定义 (API Reference)
+
+**Base URL**: `/api/v1`
+
+### 3.1 消息发送 (Send Message)
+
+支持用户私信和应用通知的统一发送接口。
+
+*   **Endpoint**: `POST /messages/`
+*   **Auth**: User Token 或 App Signature
+*   **Request Body**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :--- | :--- |
+| `receiver_id` | int | 选填 | 接收者统一用户ID (与 `app_user_id` 二选一) |
+| `app_id` | int | 选填 | 应用ID (若使用 `app_user_id` 则必填) |
+| `app_user_id` | string | 选填 | 第三方业务系统账号 (需已建立映射) |
+| `type` | string | 是 | `MESSAGE` 或 `NOTIFICATION` |
+| `content_type` | string | 是 | `TEXT`, `IMAGE`, `VIDEO`, `FILE` |
+| `title` | string | 是 | 标题 |
+| `content` | string/json | 是 | 文本内容或文件元数据 JSON |
+| `action_url` | string | 否 | 原始跳转链接 |
+| `action_text` | string | 否 | 按钮文案 |
+| `auto_sso` | bool | 否 | 是否自动封装 SSO 跳转 (默认为 False) |
+| `target_url` | string | 否 | 若 `auto_sso=true`,此为最终业务目标 URL |
+
+*   **Example Request (应用发送通知)**:
+```json
+{
+  "app_id": 101,
+  "app_user_id": "zhangsan_oa",
+  "type": "NOTIFICATION",
+  "content_type": "TEXT",
+  "title": "OA审批提醒",
+  "content": "您有一条新的报销单待审批",
+  "auto_sso": true,
+  "target_url": "http://oa.example.com/audit/123",
+  "action_text": "立即处理"
+}
+```
+
+*   **Response (200 OK)**:
+```json
+{
+  "id": 501,
+  "type": "NOTIFICATION",
+  "content": "您有一条新的报销单待审批",
+  "action_url": "http://api.platform.com/api/v1/auth/sso/jump?app_id=101&redirect_to=...",
+  "created_at": "2026-02-23T10:00:00"
+}
+```
+
+### 3.2 获取会话列表 (Get Conversations)
+
+获取当前用户的会话聚合列表(类似微信首页)。
+
+*   **Endpoint**: `GET /messages/conversations`
+*   **Auth**: User Token Only
+*   **Response**: List of Conversations
+
+```json
+[
+  {
+    "user_id": 0,
+    "username": "System",
+    "full_name": "系统通知",
+    "unread_count": 5,
+    "last_message": "您的密码已重置",
+    "last_message_type": "TEXT",
+    "updated_at": "2026-02-23T10:05:00"
+  },
+  {
+    "user_id": 102,
+    "username": "13800138000",
+    "full_name": "李四",
+    "unread_count": 0,
+    "last_message": "[IMAGE]",
+    "last_message_type": "IMAGE",
+    "updated_at": "2026-02-22T18:30:00"
+  }
+]
+```
+
+### 3.3 获取聊天记录 (Get History)
+
+*   **Endpoint**: `GET /messages/history/{other_user_id}`
+*   **Params**:
+    *   `other_user_id`: 对方用户ID (0 代表系统通知)
+    *   `skip`: 分页偏移 (默认0)
+    *   `limit`: 条数 (默认50)
+*   **Response**: List of Messages
+
+### 3.4 消息状态管理
+
+*   **获取未读数**: `GET /messages/unread-count`
+*   **标记单条已读**: `PUT /messages/{message_id}/read`
+*   **标记全部已读**: `PUT /messages/read-all`
+*   **删除消息**: `DELETE /messages/{message_id}`
+
+### 3.5 文件上传 (Upload)
+
+*   **Endpoint**: `POST /messages/upload`
+*   **Content-Type**: `multipart/form-data`
+*   **Form Field**: `file`
+*   **Response**:
+```json
+{
+  "url": "messages/1/2026/02/uuid.jpg", 
+  "filename": "image.jpg",
+  "content_type": "image/jpeg",
+  "size": 50200
+}
+```
+> **注意**: 返回的 `url` 是 MinIO 对象 Key,发送消息时应将此 JSON 作为 `content` 发送。
+
+### 3.6 SSO 跳转 (SSO Jump)
+
+*   **Endpoint**: `GET /auth/sso/jump`
+*   **Params**:
+    *   `app_id`: 目标应用 ID
+    *   `redirect_to`: 最终跳转地址
+*   **逻辑**: 验证登录态 -> 生成 Ticket -> 302 跳转到 `AppCallbackUrl?ticket=...&next=redirect_to`。
+
+---
+
+## 4. WebSocket 实时接口
+
+*   **URL**: `ws://{host}/api/v1/ws/messages`
+*   **Query Param**: `token={JWT_TOKEN}`
+*   **Events**:
+    *   **Server -> Client**:
+        ```json
+        {
+          "type": "NEW_MESSAGE",
+          "data": {
+            "id": 502,
+            "sender_id": 102,
+            "title": "新消息",
+            "content": "...",
+            "content_type": "TEXT",
+            "created_at": "..."
+          }
+        }
+        ```
+    *   **Client -> Server**:
+        *   `ping`: 心跳包,服务器回复 `pong`。
+
+---
+
+## 5. 枚举定义
+
+### MessageType
+*   `MESSAGE`: 普通用户私信
+*   `NOTIFICATION`: 系统/应用通知
+
+### ContentType
+*   `TEXT`: 纯文本
+*   `IMAGE`: 图片
+*   `VIDEO`: 视频
+*   `FILE`: 普通文件

+ 81 - 0
frontend/src/composables/useHelpDocs.ts

@@ -0,0 +1,81 @@
+import { ElMessage } from 'element-plus'
+
+export function useHelpDocs() {
+  const getApiBaseUrl = () => {
+    // 1. 优先尝试使用环境变量
+    let baseUrl = import.meta.env.VITE_API_BASE_URL
+    
+    // 2. 如果没有环境变量或为相对路径,基于当前 window.location 构建
+    if (!baseUrl) {
+      // 假设后端 API 挂载在 /api/v1 下 (常见 Nginx 代理或 Docker 配置)
+      baseUrl = window.location.origin + '/api/v1'
+    } else if (baseUrl.startsWith('/')) {
+      baseUrl = window.location.origin + baseUrl
+    }
+
+    // 3. 特殊处理: 如果是 localhost 但当前通过 IP 访问,尝试自动替换 hostname
+    // 这样开发者在局域网测试时,下载的文档会显示局域网 IP
+    if (baseUrl.includes('localhost') && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
+        baseUrl = baseUrl.replace('localhost', window.location.hostname)
+    }
+
+    // 去除末尾斜杠,保证拼接一致性
+    if (baseUrl.endsWith('/')) {
+      baseUrl = baseUrl.slice(0, -1)
+    }
+    
+    return baseUrl
+  }
+
+  const openSwagger = () => {
+    const baseUrl = getApiBaseUrl()
+    // 后端 main.py 配置: docs_url=f"{settings.API_V1_STR}/docs"
+    // API_V1_STR 默认为 /api/v1
+    // 所以 Swagger 地址为 .../api/v1/docs
+    const swaggerUrl = `${baseUrl}/docs`
+    window.open(swaggerUrl, '_blank')
+  }
+
+  const downloadDoc = async (filePath: string, fileName: string) => {
+    try {
+      const response = await fetch(filePath)
+      if (!response.ok) throw new Error('Network response was not ok')
+      
+      let text = await response.text()
+
+      const baseUrl = getApiBaseUrl()
+
+      // 执行替换
+      // 替换文档中的 {{API_BASE_URL}} 占位符
+      text = text.replace(/{{API_BASE_URL}}/g, baseUrl)
+      
+      // 兼容处理:替换可能遗漏的 http://localhost:8000/api/v1
+      text = text.replace(/http:\/\/localhost:8000\/api\/v1/g, baseUrl)
+
+      // 创建并下载 Blob
+      const blob = new Blob([text], { type: 'text/markdown' })
+      const url = window.URL.createObjectURL(blob)
+      
+      const link = document.createElement('a')
+      link.href = url
+      link.download = fileName
+      document.body.appendChild(link)
+      link.click()
+      
+      // 清理
+      document.body.removeChild(link)
+      window.URL.revokeObjectURL(url)
+      
+      ElMessage.success('文档下载成功,已更新 API 地址')
+    } catch (error) {
+      console.error('Download failed:', error)
+      ElMessage.error('文档下载失败,请检查网络或文件路径')
+    }
+  }
+
+  return {
+    getApiBaseUrl,
+    openSwagger,
+    downloadDoc
+  }
+}

文件差異過大導致無法顯示
+ 11 - 1692
frontend/src/views/Help.vue


+ 123 - 0
frontend/src/views/help/AccountSync.vue

@@ -0,0 +1,123 @@
+<template>
+  <div class="help-content">
+    <div class="content-header">
+      <h2>账号同步 (M2M) 使用说明</h2>
+      <el-button type="primary" size="small" plain @click="downloadDoc('/docs/account_sync.md', 'Account_Sync_Guide.md')">
+        <el-icon style="margin-right: 5px"><Download /></el-icon>
+        下载 开发文档
+      </el-button>
+    </div>
+    <p class="intro">此接口用于将外部业务系统(如 OA、CRM)的用户账号关系同步到本平台。支持批量调用,实现“本平台用户(手机号)”与“外部应用账号(ID/邮箱)”的绑定。</p>
+
+    <div class="section">
+      <h3>1. 接口概述</h3>
+      <ul>
+        <li><strong>接口地址</strong>: <code>POST /api/v1/apps/mapping/sync</code></li>
+        <li><strong>认证方式</strong>: 请求头需包含 <code>X-App-Access-Token</code>(可在应用详情页查看)。</li>
+        <li><strong>支持操作</strong>: 增改 (UPSERT) 和 删除 (DELETE)。</li>
+      </ul>
+      
+      <div class="feature-card">
+        <h4>🛠️ 接口逻辑 (UPSERT 模式)</h4>
+        <ol>
+          <li>根据 <code>mobile</code> 查找用户。</li>
+          <li><strong>新建用户</strong>:若用户不存在,必须提供 <code>name</code> 和 <code>english_name</code>,否则报错。</li>
+          <li><strong>已有用户</strong>:若提供了 <code>name</code> 或 <code>english_name</code> 且不为空,则更新用户信息;否则保留原值。</li>
+          <li>将该用户与当前应用建立映射关系(绑定 <code>mapped_key</code> 和 <code>mapped_email</code>)。</li>
+        </ol>
+      </div>
+
+      <div class="feature-card">
+        <h4>🗑️ 删除逻辑 (DELETE 模式)</h4>
+        <ul>
+          <li>仅删除用户在当前应用下的映射关系。</li>
+          <li><strong>不删除</strong>平台上的用户账号。</li>
+        </ul>
+      </div>
+    </div>
+
+    <div class="section">
+      <h3>2. 请求参数 (JSON Body)</h3>
+      <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><span class="tag-required">是</span></td><td>用户手机号(平台唯一标识)</td></tr>
+          <tr><td><code>name</code></td><td>string</td><td><span class="tag-conditional">条件</span></td><td>姓名(新建用户必填)</td></tr>
+          <tr><td><code>english_name</code></td><td>string</td><td><span class="tag-conditional">条件</span></td><td>英文名(新建用户必填,全局唯一)</td></tr>
+          <tr><td><code>mapped_key</code></td><td>string</td><td><span class="tag-optional">否</span></td><td>外部系统中的用户ID(在该应用下唯一)</td></tr>
+          <tr><td><code>mapped_email</code></td><td>string</td><td><span class="tag-optional">否</span></td><td>外部系统中的邮箱(在该应用下唯一)</td></tr>
+          <tr><td><code>is_active</code></td><td>boolean</td><td><span class="tag-optional">否</span></td><td>映射关系状态(<code>true</code>启用,<code>false</code>禁用)</td></tr>
+          <tr><td><code>sync_action</code></td><td>string</td><td><span class="tag-optional">否</span></td><td><code>UPSERT</code> (默认) 或 <code>DELETE</code></td></tr>
+        </tbody>
+      </table>
+
+      <p><strong>请求示例 (UPSERT):</strong></p>
+      <div class="code-block">
+        <pre>
+curl -X POST "http://your-uap-domain/api/v1/apps/mapping/sync" \
+     -H "Content-Type: application/json" \
+     -H "X-App-Access-Token: YOUR_APP_ACCESS_TOKEN" \
+     -d '{
+           "mobile": "13800138000",
+           "name": "张三",
+           "english_name": "zhangsan",
+           "mapped_key": "user_1001",
+           "mapped_email": "zhangsan@example.com",
+           "sync_action": "UPSERT"
+         }'
+        </pre>
+      </div>
+
+      <p><strong>请求示例 (DELETE):</strong></p>
+      <div class="code-block">
+        <pre>
+curl -X POST "http://your-uap-domain/api/v1/apps/mapping/sync" \
+     -H "Content-Type: application/json" \
+     -H "X-App-Access-Token: YOUR_APP_ACCESS_TOKEN" \
+     -d '{
+           "mobile": "13800138000",
+           "sync_action": "DELETE"
+         }'
+        </pre>
+      </div>
+    </div>
+
+    <div class="section">
+      <h3>3. 响应说明</h3>
+      <p><strong>响应成功 (200 OK):</strong></p>
+      <div class="code-block">
+        <pre>
+{
+  "id": 123,
+  "app_id": 1,
+  "user_id": 456,
+  "mapped_key": "user_1001",
+  "mapped_email": "zhangsan@example.com",
+  "user_mobile": "13800138000",
+  "user_status": "ACTIVE",
+  "is_active": true
+}
+        </pre>
+      </div>
+
+      <p><strong>响应失败:</strong></p>
+      <ul>
+        <li><code>400 Bad Request</code>: <code>mapped_key</code> 或 <code>mapped_email</code> 已被其他用户占用。</li>
+        <li><code>403 Forbidden</code>: Access Token 无效或过期。</li>
+      </ul>
+    </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>

+ 336 - 0
frontend/src/views/help/ApiComparison.vue

@@ -0,0 +1,336 @@
+<template>
+  <div class="help-content">
+    <div class="content-header">
+      <h2>接口对比:/login vs /sso-login</h2>
+      <el-button type="primary" size="small" plain @click="downloadDoc('/docs/api_comparison.md', 'API_Comparison_Guide.md')">
+        <el-icon style="margin-right: 5px"><Download /></el-icon>
+        下载 开发文档
+      </el-button>
+    </div>
+    <p class="intro">平台提供了两个登录接口,它们在功能上有一定重叠,但<strong>设计目的和使用场景不同</strong>。本章节详细对比两个接口的区别,帮助您选择最合适的接口。</p>
+
+    <div class="section">
+      <h3>1. /login 接口(通用登录)</h3>
+      
+      <div class="feature-card">
+        <h4>✅ 适用场景</h4>
+        <ul>
+          <li>后端服务器直接调用(推荐使用签名验证)</li>
+          <li>需要签名验证保证安全性的场景</li>
+          <li>支持所有类型的应用(SIMPLE_API / OIDC)</li>
+          <li>既支持平台登录,也支持应用 SSO 登录</li>
+        </ul>
+      </div>
+
+      <div class="feature-card">
+        <h4>🔑 接口特点</h4>
+        <ul>
+          <li><strong>双模式设计</strong>:根据是否提供 <code>app_id</code> 返回不同结果
+            <ul>
+              <li>不提供 <code>app_id</code>:返回平台 <code>access_token</code>(用于访问平台管理功能)</li>
+              <li>提供 <code>app_id</code>:返回应用 <code>ticket</code>(用于应用 SSO 登录)</li>
+            </ul>
+          </li>
+          <li><strong>支持签名验证</strong>:<code>sign</code> 和 <code>timestamp</code> 可选,但推荐使用</li>
+          <li><strong>返回原始票据</strong>:只返回 <code>ticket</code>,需要调用方自己构建跳转 URL</li>
+        </ul>
+      </div>
+
+      <h4>📝 请求参数</h4>
+      <table class="param-table">
+        <thead>
+          <tr><th>字段</th><th>必填</th><th>说明</th></tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td><code>app_id</code></td>
+            <td><span class="tag-optional">选填</span></td>
+            <td>不提供时为平台登录,提供时为应用 SSO 登录</td>
+          </tr>
+          <tr>
+            <td><code>identifier</code></td>
+            <td><span class="tag-required">必填</span></td>
+            <td>用户标识:手机号、映射key或映射email</td>
+          </tr>
+          <tr>
+            <td><code>password</code></td>
+            <td><span class="tag-required">必填</span></td>
+            <td>用户密码</td>
+          </tr>
+          <tr>
+            <td><code>sign</code></td>
+            <td><span class="tag-optional">选填</span></td>
+            <td>签名(如果提供,必须同时提供timestamp)</td>
+          </tr>
+          <tr>
+            <td><code>timestamp</code></td>
+            <td><span class="tag-optional">选填</span></td>
+            <td>时间戳(如果提供,必须同时提供sign)</td>
+          </tr>
+        </tbody>
+      </table>
+
+      <h4>📤 响应示例</h4>
+      <p><strong>场景1:应用 SSO 登录(提供 app_id)</strong></p>
+      <div class="code-block">
+        <pre>
+// 请求
+{
+  "app_id": "your_app_id",
+  "identifier": "13800138000",
+  "password": "user_password_123",
+  "timestamp": 1709876543,
+  "sign": "a1b2c3d4e5..." 
+}
+
+// 响应
+{
+  "ticket": "TICKET-7f8e9d0a-..."
+}
+        </pre>
+      </div>
+
+      <p><strong>场景2:平台登录(不提供 app_id)</strong></p>
+      <div class="code-block">
+        <pre>
+// 请求
+{
+  "identifier": "13800138000",
+  "password": "admin_password_123"
+}
+
+// 响应
+{
+  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+  "token_type": "bearer",
+  "role": "SUPER_ADMIN"
+}
+        </pre>
+      </div>
+    </div>
+
+    <el-divider />
+
+    <div class="section">
+      <h3>2. /sso-login 接口(简易 SSO 登录)</h3>
+      
+      <div class="feature-card">
+        <h4>✅ 适用场景</h4>
+        <ul>
+          <li>前端浏览器调用(简化跳转流程)</li>
+          <li>支持基于会话的单点登录(用户已在平台登录)</li>
+          <li>快速实现跨应用免登录</li>
+          <li><span class="danger">⚠️ 仅支持 SIMPLE_API 类型的应用</span>(OIDC 应用请使用标准流程)</li>
+        </ul>
+      </div>
+
+      <div class="feature-card">
+        <h4>🔑 接口特点</h4>
+        <ul>
+          <li><strong>双认证模式</strong>:
+            <ol>
+              <li><strong>会话认证优先</strong>:如果用户已登录 UAP 平台(携带有效 token),自动使用当前用户身份</li>
+              <li><strong>凭据认证备用</strong>:如果未登录,可以提供 <code>username</code> 和 <code>password</code> 进行认证</li>
+            </ol>
+          </li>
+          <li><strong>直接返回跳转 URL</strong>:返回完整的 <code>redirect_url</code>,前端直接跳转即可</li>
+          <li><strong>无需签名</strong>:简化调用流程,适合前端使用</li>
+          <li><strong>应用类型限制</strong>:只支持 <code>SIMPLE_API</code> 应用</li>
+        </ul>
+      </div>
+
+      <h4>📝 请求参数</h4>
+      <table class="param-table">
+        <thead>
+          <tr><th>字段</th><th>必填</th><th>说明</th></tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td><code>app_id</code></td>
+            <td><span class="tag-required">必填</span></td>
+            <td>应用ID(只支持 SIMPLE_API 类型)</td>
+          </tr>
+          <tr>
+            <td><code>username</code></td>
+            <td><span class="tag-conditional">条件必填</span></td>
+            <td>用户名(如果用户未登录UAP,则必须提供)</td>
+          </tr>
+          <tr>
+            <td><code>password</code></td>
+            <td><span class="tag-conditional">条件必填</span></td>
+            <td>密码(如果用户未登录UAP,则必须提供)</td>
+          </tr>
+        </tbody>
+      </table>
+
+      <h4>📤 使用示例</h4>
+      <p><strong>场景1:用户已在 UAP 登录(会话认证)</strong></p>
+      <div class="code-block">
+        <pre>
+// JavaScript 前端代码
+const response = await fetch('/api/v1/simple/sso-login', {
+  method: 'POST',
+  headers: {
+    'Content-Type': 'application/json',
+    'Authorization': 'Bearer ' + localStorage.getItem('access_token')
+  },
+  body: JSON.stringify({ 
+    app_id: 'my_app_123' 
+  })
+});
+
+const { redirect_url } = await response.json();
+window.location.href = redirect_url;  // 直接跳转
+
+// 响应示例
+{
+  "redirect_url": "https://your-app.com/callback?ticket=TICKET-xxx"
+}
+        </pre>
+      </div>
+
+      <p><strong>场景2:用户未登录(凭据认证)</strong></p>
+      <div class="code-block">
+        <pre>
+// JavaScript 前端代码
+const response = await fetch('/api/v1/simple/sso-login', {
+  method: 'POST',
+  headers: { 'Content-Type': 'application/json' },
+  body: JSON.stringify({
+    app_id: 'my_app_123',
+    username: '13800138000',
+    password: 'password123'
+  })
+});
+
+const { redirect_url } = await response.json();
+window.location.href = redirect_url;  // 直接跳转
+
+// 响应示例
+{
+  "redirect_url": "https://your-app.com/callback?ticket=TICKET-xxx"
+}
+        </pre>
+      </div>
+    </div>
+
+    <el-divider />
+
+    <div class="section">
+      <h3>3. 核心区别总结</h3>
+      <table class="comparison-table">
+        <thead>
+          <tr>
+            <th style="width: 20%">特性</th>
+            <th style="width: 40%">/login</th>
+            <th style="width: 40%">/sso-login</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td><strong>认证方式</strong></td>
+            <td>只支持用户名+密码</td>
+            <td><span class="highlight">支持会话认证 OR 用户名+密码</span></td>
+          </tr>
+          <tr>
+            <td><strong>签名验证</strong></td>
+            <td><span class="success">✅ 支持(可选)</span></td>
+            <td>❌ 不支持</td>
+          </tr>
+          <tr>
+            <td><strong>应用类型限制</strong></td>
+            <td>❌ 无限制</td>
+            <td><span class="warning">⚠️ 只支持 SIMPLE_API</span></td>
+          </tr>
+          <tr>
+            <td><strong>返回值</strong></td>
+            <td><code>ticket</code> 或 <code>access_token</code></td>
+            <td><span class="highlight"><code>redirect_url</code>(含 ticket)</span></td>
+          </tr>
+          <tr>
+            <td><strong>会话支持</strong></td>
+            <td>❌ 不支持</td>
+            <td><span class="success">✅ 支持(优先使用)</span></td>
+          </tr>
+          <tr>
+            <td><strong>适用端</strong></td>
+            <td>后端服务器</td>
+            <td><span class="highlight">前端浏览器</span></td>
+          </tr>
+          <tr>
+            <td><strong>使用复杂度</strong></td>
+            <td>中等(需要自己构建跳转)</td>
+            <td><span class="success">简单(直接跳转)</span></td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <div class="section">
+      <h3>4. 选择建议</h3>
+      <table class="recommendation-table">
+        <thead>
+          <tr>
+            <th style="width: 35%">使用场景</th>
+            <th style="width: 25%">推荐接口</th>
+            <th style="width: 40%">原因</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td>后端服务器调用</td>
+            <td><span class="recommend-badge">/login</span></td>
+            <td>支持签名验证,安全性更高</td>
+          </tr>
+          <tr>
+            <td>前端浏览器跳转</td>
+            <td><span class="recommend-badge">/sso-login</span></td>
+            <td>直接返回跳转 URL,简化流程</td>
+          </tr>
+          <tr>
+            <td>单点登录(已登录用户)</td>
+            <td><span class="recommend-badge">/sso-login</span></td>
+            <td>支持会话认证,无需再次输入密码</td>
+          </tr>
+          <tr>
+            <td>OIDC 应用</td>
+            <td><span class="recommend-badge">/login</span></td>
+            <td>/sso-login 不支持 OIDC</td>
+          </tr>
+          <tr>
+            <td>需要平台管理功能</td>
+            <td><span class="recommend-badge">/login</span></td>
+            <td>获取平台 access_token(不提供 app_id)</td>
+          </tr>
+          <tr>
+            <td>跨应用免登录</td>
+            <td><span class="recommend-badge">/sso-login</span> 或 <span class="recommend-badge">/exchange</span></td>
+            <td>取决于是否需要服务端签名</td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <el-alert title="💡 最佳实践建议" type="info" :closable="false" show-icon class="alert-box">
+      <div>
+        <ul style="margin: 0; padding-left: 20px;">
+          <li>如果您的应用是<strong>前端单页应用(SPA)</strong>且需要实现单点登录,推荐使用 <code>/sso-login</code></li>
+          <li>如果您需要从<strong>服务器端</strong>进行认证并需要更高的安全性,推荐使用 <code>/login</code> 接口(带签名)</li>
+          <li>对于用户已经在 UAP 平台登录的场景,<code>/sso-login</code> 可以提供<strong>无缝的跳转体验</strong></li>
+          <li>如果您的应用使用 <strong>OIDC 协议</strong>,请使用 <code>/login</code> 或标准的 OIDC 授权码流程</li>
+        </ul>
+      </div>
+    </el-alert>
+  </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>

+ 588 - 0
frontend/src/views/help/CustomLogin.vue

@@ -0,0 +1,588 @@
+<template>
+  <div class="help-content">
+    <div class="content-header">
+      <h2>统一认证平台 - 简易认证 (Simple Auth) 集成指南</h2>
+      <el-button type="primary" size="small" plain @click="downloadDoc('/docs/simple_auth.md', 'Simple_Auth_Guide.md')">
+        <el-icon style="margin-right: 5px"><Download /></el-icon>
+        下载 开发文档
+      </el-button>
+    </div>
+    <p class="intro">本指南适用于需要使用自定义登录页面(而非跳转到认证中心标准页面),并通过后端 API 直接进行用户认证的场景。</p>
+
+    <div class="section">
+      <h3>1. 核心流程图</h3>
+      <div class="flow-chart">
+        <div class="actor-row">
+          <div class="actor">用户 (User)</div>
+          <div class="actor">客户端 (Client)</div>
+          <div class="actor">统一认证平台 (UAP)</div>
+        </div>
+        
+        <div class="step">
+          <div class="arrow user-to-client">1. 输入账号/密码</div>
+        </div>
+        <div class="step">
+          <div class="self-action">2. 生成签名 (Sign)</div>
+        </div>
+        <div class="step">
+          <div class="arrow client-to-uap">3. POST /login (账号+密码+签名)</div>
+        </div>
+        <div class="step">
+          <div class="arrow uap-to-client dashed">4. 返回 Ticket (票据)</div>
+        </div>
+        <div class="step">
+          <div class="self-action">5. 内部逻辑处理</div>
+        </div>
+        <div class="step">
+          <div class="arrow client-to-uap">6. POST /validate (Ticket+签名)</div>
+        </div>
+        <div class="step">
+          <div class="arrow uap-to-client dashed">7. 返回 用户信息 (ID, Mobile...)</div>
+        </div>
+        <div class="step">
+          <div class="arrow client-to-user dashed">8. 登录成功</div>
+        </div>
+      </div>
+    </div>
+
+    <div class="section">
+      <h3>2. 前置准备</h3>
+      <p>在调用 API 之前,请确保您已在平台注册应用并获取以下信息:</p>
+      <ul>
+        <li><strong>App ID (<code>app_id</code>)</strong>: 应用唯一标识。</li>
+        <li><strong>App Secret (<code>app_secret</code>)</strong>: 应用密钥(<strong class="danger">严禁泄露给前端</strong>)。</li>
+      </ul>
+      <el-alert title="安全警告" type="error" :closable="false" show-icon class="alert-box">
+        <div>由于生成签名需要使用 <code>App Secret</code>,建议登录请求由您的<strong>应用后端</strong>发起,或者使用后端代理(BFF模式)。如果在前端(浏览器 JS)直接存储 Secret 并计算签名,极易导致密钥泄露。</div>
+      </el-alert>
+    </div>
+
+    <div class="section">
+      <h3>3. 签名算法 (Signature)</h3>
+      <p>所有涉及安全的接口都需要校验签名。</p>
+      <p><strong>签名生成步骤:</strong></p>
+      <ol>
+        <li><strong>准备参数</strong>:收集所有请求参数(不包括 <code>sign</code> 本身)。</li>
+        <li><strong>排序</strong>:按照参数名(key)的 ASCII 码从小到大排序。</li>
+        <li><strong>拼接</strong>:将排序后的参数拼接成 <code>key1=value1&key2=value2...</code> 格式的字符串。</li>
+        <li><strong>计算 HMAC</strong>:使用 <code>App Secret</code> 作为密钥,对拼接字符串进行 <strong>HMAC-SHA256</strong> 计算。</li>
+        <li><strong>转十六进制</strong>:将计算结果转换为 Hex 字符串即为签名。</li>
+      </ol>
+
+      <p><strong>Python 示例代码:</strong></p>
+      <div class="code-block">
+        <pre>
+import hmac
+import hashlib
+import time
+
+def generate_signature(secret: str, params: dict) -> str:
+    # 1. 过滤掉空值和 sign 字段 (如果有)
+    data = {k: v for k, v in params.items() if k != "sign" and v is not None}
+    
+    # 2. 排序 key
+    sorted_keys = sorted(data.keys())
+    
+    # 3. 拼接字符串
+    query_string = "&".join([f"{k}={data[k]}" for k in sorted_keys])
+    
+    # 4. 计算 HMAC-SHA256
+    signature = hmac.new(
+        secret.encode('utf-8'),
+        query_string.encode('utf-8'),
+        hashlib.sha256
+    ).hexdigest()
+    
+    return signature
+        </pre>
+      </div>
+    </div>
+
+    <div class="section">
+      <h3>4. 接口开发详解</h3>
+
+      <h4>4.1 第一步:密码登录获取票据 (Login)</h4>
+      <p>用户在界面输入账号密码后,调用此接口获取临时票据(Ticket)。</p>
+      <ul>
+        <li><strong>接口地址</strong>: <code>POST /api/v1/simple/login</code></li>
+        <li><strong>Content-Type</strong>: <code>application/json</code></li>
+      </ul>
+
+      <p><strong>请求参数 (JSON Body):</strong></p>
+      <table class="param-table">
+        <thead>
+          <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>
+        </thead>
+        <tbody>
+          <tr><td><code>app_id</code></td><td>string</td><td>是</td><td>您的应用 ID</td></tr>
+          <tr><td><code>identifier</code></td><td>string</td><td>是</td><td>用户标识(手机号、用户名或邮箱)</td></tr>
+          <tr><td><code>password</code></td><td>string</td><td>是</td><td>用户明文密码</td></tr>
+          <tr><td><code>timestamp</code></td><td>int</td><td>是</td><td>当前时间戳(秒),有效期 300秒</td></tr>
+          <tr><td><code>sign</code></td><td>string</td><td>是</td><td>签名字符串</td></tr>
+        </tbody>
+      </table>
+
+      <p><strong>请求示例:</strong></p>
+      <div class="code-block">
+        <pre>
+{
+  "app_id": "your_app_id",
+  "identifier": "13800138000",
+  "password": "user_password_123",
+  "timestamp": 1709876543,
+  "sign": "a1b2c3d4e5..." 
+}
+        </pre>
+      </div>
+
+      <p><strong>响应成功 (200 OK):</strong></p>
+      <div class="code-block">
+        <pre>
+{
+  "ticket": "TICKET-7f8e9d0a-..." 
+}
+        </pre>
+      </div>
+
+      <el-divider />
+
+      <h4>4.2 (可选) 替代方案:验证码登录获取票据 (SMS Login)</h4>
+      <p>除了密码登录,您还可以使用手机验证码进行登录获取 Ticket。</p>
+      <ul>
+        <li><strong>接口地址</strong>: <code>POST /api/v1/simple/sms-login</code></li>
+        <li><strong>Content-Type</strong>: <code>application/json</code></li>
+      </ul>
+
+      <p><strong>请求参数 (JSON Body):</strong></p>
+      <table class="param-table">
+        <thead>
+          <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>
+        </thead>
+        <tbody>
+          <tr><td><code>app_id</code></td><td>string</td><td>是</td><td>您的应用 ID</td></tr>
+          <tr><td><code>mobile</code></td><td>string</td><td>是</td><td>用户手机号</td></tr>
+          <tr><td><code>code</code></td><td>string</td><td>是</td><td>短信验证码</td></tr>
+          <tr><td><code>timestamp</code></td><td>int</td><td>是</td><td>当前时间戳</td></tr>
+          <tr><td><code>sign</code></td><td>string</td><td>是</td><td>签名字符串</td></tr>
+        </tbody>
+      </table>
+      
+      <p><strong>注意:</strong> 在调用此接口前,需先调用 <code>/api/v1/auth/sms/send-code</code> 发送验证码。</p>
+
+      <p><strong>请求示例:</strong></p>
+      <div class="code-block">
+        <pre>
+{
+  "app_id": "your_app_id",
+  "mobile": "13800138000",
+  "code": "123456",
+  "timestamp": 1709876543,
+  "sign": "a1b2c3d4e5..." 
+}
+        </pre>
+      </div>
+
+      <p><strong>响应成功 (200 OK):</strong></p>
+      <div class="code-block">
+        <pre>
+{
+  "ticket": "TICKET-7f8e9d0a-..." 
+}
+        </pre>
+      </div>
+
+      <el-divider />
+
+      <h4>4.3 第二步:验证票据并获取用户信息 (Validate)</h4>
+      <p>拿到 <code>ticket</code> 后,立即调用此接口解析出用户身份。</p>
+      <ul>
+        <li><strong>接口地址</strong>: <code>POST /api/v1/simple/validate</code></li>
+        <li><strong>Content-Type</strong>: <code>application/json</code></li>
+      </ul>
+
+      <p><strong>请求参数 (JSON Body):</strong></p>
+      <table class="param-table">
+        <thead>
+          <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>
+        </thead>
+        <tbody>
+          <tr><td><code>app_id</code></td><td>string</td><td>是</td><td>您的应用 ID</td></tr>
+          <tr><td><code>ticket</code></td><td>string</td><td>是</td><td>上一步获取到的票据</td></tr>
+          <tr><td><code>timestamp</code></td><td>int</td><td>是</td><td>当前时间戳</td></tr>
+          <tr><td><code>sign</code></td><td>string</td><td>是</td><td>签名(注意参数变化,需重新计算)</td></tr>
+        </tbody>
+      </table>
+
+      <p><strong>请求示例:</strong></p>
+      <div class="code-block">
+        <pre>
+{
+  "app_id": "your_app_id",
+  "ticket": "TICKET-7f8e9d0a-...",
+  "timestamp": 1709876545,
+  "sign": "f9e8d7c6b5..."
+}
+        </pre>
+      </div>
+
+      <p><strong>响应成功 (200 OK):</strong></p>
+      <p>此接口返回 <code>valid: true</code> 表示票据有效,并附带用户数据。</p>
+      <div class="code-block">
+        <pre>
+{
+  "valid": true,
+  "user_id": 1001,
+  "mobile": "13800138000",
+  "mapped_key": "user_zhangsan",  // 第三方映射ID(如果有)
+  "mapped_email": "zhangsan@example.com" // 映射邮箱(如果有)
+}
+        </pre>
+      </div>
+
+      <p><strong>响应失败 (票据无效或过期):</strong></p>
+      <div class="code-block">
+        <pre>
+{
+  "valid": false
+}
+        </pre>
+      </div>
+    </div>
+
+    <div class="section">
+      <h3>5. 多语言调用示例</h3>
+      <p>以下提供了多种编程语言计算签名并发起请求的示例代码。</p>
+      
+      <el-tabs type="border-card" class="code-tabs">
+        <el-tab-pane label="Python">
+          <div class="code-block">
+            <pre>
+import requests
+import time
+import hmac
+import hashlib
+import json
+
+# 配置信息
+API_BASE = "http://localhost:8000/api/v1/simple"
+APP_ID = "test_app_001"
+APP_SECRET = "secret_key_abc123" # 务必保密
+
+def get_sign(params):
+    # 排除 sign 字段
+    data = {k: v for k, v in params.items() if k != "sign"}
+    # 排序并拼接
+    query_string = "&".join([f"{k}={data[k]}" for k in sorted(data.keys())])
+    # HMAC-SHA256
+    return hmac.new(APP_SECRET.encode(), query_string.encode(), hashlib.sha256).hexdigest()
+
+def main():
+    # === 步骤 1: 登录获取 Ticket ===
+    login_ts = int(time.time())
+    login_payload = {
+        "app_id": APP_ID,
+        "identifier": "13800000001",
+        "password": "password123",
+        "timestamp": login_ts
+    }
+    # 计算签名
+    login_payload["sign"] = get_sign(login_payload)
+
+    print(f"1. 正在尝试登录: {login_payload['identifier']} ...")
+    resp = requests.post(f"{API_BASE}/login", json=login_payload)
+    
+    if resp.status_code != 200:
+        print(f"登录失败: {resp.text}")
+        return
+
+    ticket = resp.json().get("ticket")
+    print(f"登录成功! 获取到 Ticket: {ticket}")
+
+    # === 步骤 2: 使用 Ticket 换取用户信息 ===
+    validate_ts = int(time.time())
+    validate_payload = {
+        "app_id": APP_ID,
+        "ticket": ticket,
+        "timestamp": validate_ts
+    }
+    # 重新计算签名(参数变了,签名必须重算)
+    validate_payload["sign"] = get_sign(validate_payload)
+
+    print(f"\n2. 正在验证 Ticket...")
+    v_resp = requests.post(f"{API_BASE}/validate", json=validate_payload)
+    
+    user_info = v_resp.json()
+    if user_info.get("valid"):
+        print("验证成功! 用户信息如下:")
+        print(json.dumps(user_info, indent=2, ensure_ascii=False))
+    else:
+        print("Ticket 无效或已过期")
+
+if __name__ == "__main__":
+    main()
+            </pre>
+          </div>
+        </el-tab-pane>
+        
+        <el-tab-pane label="Java">
+          <div class="code-block">
+            <pre>
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.util.*;
+import java.nio.charset.StandardCharsets;
+
+public class AuthExample {
+    private static final String SECRET = "secret_key_abc123";
+
+    public static String generateSign(Map&lt;String, String&gt; params) {
+        try {
+            // 1. 排序
+            List&lt;String&gt; sortedKeys = new ArrayList&lt;&gt;(params.keySet());
+            Collections.sort(sortedKeys);
+            
+            // 2. 拼接
+            StringBuilder sb = new StringBuilder();
+            for (String key : sortedKeys) {
+                if (!key.equals("sign") && params.get(key) != null) {
+                    if (sb.length() > 0) sb.append("&");
+                    sb.append(key).append("=").append(params.get(key));
+                }
+            }
+            
+            // 3. HMAC-SHA256
+            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
+            SecretKeySpec secret_key = new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
+            sha256_HMAC.init(secret_key);
+            
+            byte[] bytes = sha256_HMAC.doFinal(sb.toString().getBytes(StandardCharsets.UTF_8));
+            
+            // 4. Hex
+            StringBuilder hex = new StringBuilder();
+            for (byte b : bytes) {
+                hex.append(String.format("%02x", b));
+            }
+            return hex.toString();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
+    
+    public static void main(String[] args) {
+        Map&lt;String, String&gt; params = new HashMap&lt;&gt;();
+        params.put("app_id", "test_app_001");
+        params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
+        
+        System.out.println("Sign: " + generateSign(params));
+    }
+}
+            </pre>
+          </div>
+        </el-tab-pane>
+
+        <el-tab-pane label="Android (Kotlin)">
+          <div class="code-block">
+            <pre>
+import javax.crypto.Mac
+import javax.crypto.spec.SecretKeySpec
+
+object AuthUtils {
+    private const val APP_SECRET = "secret_key_abc123"
+
+    fun generateSign(params: Map&lt;String, String&gt;): String {
+        // 1. 过滤 & 排序
+        val sortedKeys = params.keys.filter { it != "sign" }.sorted()
+
+        // 2. 拼接
+        val queryString = sortedKeys.joinToString("&") { key ->
+            "$key=${params[key]}"
+        }
+
+        // 3. HMAC-SHA256
+        val hmacSha256 = "HmacSHA256"
+        val secretKeySpec = SecretKeySpec(APP_SECRET.toByteArray(Charsets.UTF_8), hmacSha256)
+        val mac = Mac.getInstance(hmacSha256)
+        mac.init(secretKeySpec)
+        
+        val bytes = mac.doFinal(queryString.toByteArray(Charsets.UTF_8))
+        
+        // 4. Hex
+        return bytes.joinToString("") { "%02x".format(it) }
+    }
+}
+
+// Usage Example
+fun main() {
+    val params = mapOf(
+        "app_id" to "test_app_001",
+        "identifier" to "13800000001",
+        "timestamp" to (System.currentTimeMillis() / 1000).toString()
+    )
+    val sign = AuthUtils.generateSign(params)
+    println("Signature: $sign")
+}
+            </pre>
+          </div>
+        </el-tab-pane>
+
+        <el-tab-pane label="JavaScript (Node.js)">
+          <div class="code-block">
+            <pre>
+const crypto = require('crypto');
+const axios = require('axios'); // npm install axios
+
+const APP_ID = 'test_app_001';
+const APP_SECRET = 'secret_key_abc123';
+const BASE_URL = 'http://localhost:8000/api/v1/simple';
+
+function getSign(params) {
+  // 1. 过滤 & 排序
+  const keys = Object.keys(params)
+    .filter(k => k !== 'sign' && params[k] !== undefined)
+    .sort();
+  
+  // 2. 拼接 Query String
+  const queryString = keys.map(k => `${k}=${params[k]}`).join('&');
+  
+  // 3. HMAC-SHA256
+  return crypto.createHmac('sha256', APP_SECRET)
+    .update(queryString)
+    .digest('hex');
+}
+
+async function login() {
+  const timestamp = Math.floor(Date.now() / 1000);
+  const payload = {
+    app_id: APP_ID,
+    identifier: '13800000001',
+    password: 'password123',
+    timestamp: timestamp
+  };
+  
+  payload.sign = getSign(payload);
+  
+  try {
+    console.log('Sending login request...');
+    const res = await axios.post(`${BASE_URL}/login`, payload);
+    console.log('Ticket:', res.data.ticket);
+  } catch (error) {
+    console.error('Login Failed:', error.response?.data || error.message);
+  }
+}
+
+login();
+            </pre>
+          </div>
+        </el-tab-pane>
+
+        <el-tab-pane label="Go">
+          <div class="code-block">
+            <pre>
+package main
+
+import (
+	"crypto/hmac"
+	"crypto/sha256"
+	"encoding/hex"
+	"fmt"
+	"sort"
+	"strings"
+	"time"
+)
+
+func GetSign(secret string, params map[string]interface{}) string {
+	// 1. 提取 Key
+	var keys []string
+	for k := range params {
+		if k != "sign" {
+			keys = append(keys, k)
+		}
+	}
+	// 2. 排序
+	sort.Strings(keys)
+
+	// 3. 拼接
+	var parts []string
+	for _, k := range keys {
+		val := fmt.Sprintf("%v", params[k])
+		parts = append(parts, fmt.Sprintf("%s=%s", k, val))
+	}
+	query := strings.Join(parts, "&")
+
+	// 4. HMAC-SHA256
+	h := hmac.New(sha256.New, []byte(secret))
+	h.Write([]byte(query))
+	return hex.EncodeToString(h.Sum(nil))
+}
+
+func main() {
+    params := map[string]interface{}{
+        "app_id":    "test_app_001",
+        "identifier": "13800000001",
+        "password":   "123456",
+        "timestamp":  time.Now().Unix(),
+    }
+    
+    secret := "secret_key_abc123"
+    sign := GetSign(secret, params)
+    fmt.Printf("Signature: %s\n", sign)
+}
+            </pre>
+          </div>
+        </el-tab-pane>
+
+        <el-tab-pane label="Swift">
+          <div class="code-block">
+            <pre>
+import Foundation
+import CommonCrypto
+
+// 注意:需要添加 Bridging Header 引入 CommonCrypto 或直接在 Linux 环境使用
+
+func hmac(string: String, key: String) -> String {
+    var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
+    CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), key, key.count, string, string.count, &digest)
+    let data = Data(digest)
+    return data.map { String(format: "%02hhx", $0) }.joined()
+}
+
+func generateSign(secret: String, params: [String: Any]) -> String {
+    // 1. 过滤 & 排序
+    let sortedKeys = params.keys.filter { $0 != "sign" }.sorted()
+    
+    // 2. 拼接
+    let queryParts = sortedKeys.map { key in
+        return "\(key)=\(params[key]!)"
+    }
+    let queryString = queryParts.joined(separator: "&")
+    
+    // 3. HMAC
+    return hmac(string: queryString, key: secret)
+}
+
+// Usage
+let params: [String: Any] = [
+    "app_id": "test_app_001",
+    "timestamp": Int(Date().timeIntervalSince1970)
+]
+let sign = generateSign(secret: "secret_123", params: params)
+print(sign)
+            </pre>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+    </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>

+ 459 - 0
frontend/src/views/help/FastIntegration.vue

@@ -0,0 +1,459 @@
+<template>
+  <div class="help-content">
+    <div class="content-header">
+      <h2>快速对接指南 (Redirect SSO)</h2>
+      <el-button type="primary" size="small" plain @click="downloadDoc('/docs/redirect_sso.md', 'Redirect_SSO_Guide.md')">
+        <el-icon style="margin-right: 5px"><Download /></el-icon>
+        下载 开发文档
+      </el-button>
+    </div>
+    <p class="intro">
+      这是最简单、最快速的集成方式。您无需开发前端登录页面,只需将用户重定向到统一认证平台,待用户登录后,平台会将携带票据 (Ticket) 的用户重定向回您的系统。
+    </p>
+
+    <div class="section">
+      <h3>1. 核心流程</h3>
+      <div class="flow-chart">
+         <div class="step">
+          <div class="self-action" style="margin-left: 0; width: auto; display: inline-block;">1. 用户访问应用 (未登录)</div>
+          <div class="arrow client-to-uap" style="position: relative; top: 0; left: 10px; width: 50px;"></div>
+          <div class="self-action" style="margin-left: 10px; width: auto; display: inline-block;">2. 跳转 UAP 登录 (?app_id=xxx)</div>
+         </div>
+         <div class="step" style="margin-top: 10px;">
+          <div class="self-action" style="margin-left: 0; width: auto; display: inline-block;">3. 用户完成登录</div>
+          <div class="arrow uap-to-client" style="position: relative; top: 0; left: 10px; width: 50px;"></div>
+          <div class="self-action" style="margin-left: 10px; width: auto; display: inline-block;">4. 回调应用 (?ticket=xxx)</div>
+         </div>
+          <div class="step" style="margin-top: 10px;">
+          <div class="self-action" style="margin-left: 0; width: auto; display: inline-block;">5. 应用后端验证 Ticket</div>
+          <div class="arrow client-to-user" style="position: relative; top: 0; left: 10px; width: 50px; border-style: dashed;"></div>
+          <div class="self-action" style="margin-left: 10px; width: auto; display: inline-block;">6. 登录成功</div>
+         </div>
+      </div>
+    </div>
+
+    <div class="section">
+      <h3>2. 详细集成步骤</h3>
+      
+      <h4>第一步:准备工作</h4>
+      <ul>
+          <li>在 UAP 管理后台创建应用。</li>
+          <li><strong>关键:</strong> 在应用配置中填写入合法的 <strong>回调地址 (Redirect URIs)</strong>。例如:<code>http://your-app.com/callback</code></li>
+          <li>获取 <code>App ID</code> 和 <code>App Secret</code> (用于后端验证 Ticket)。</li>
+      </ul>
+
+      <h4>第二步:拼接登录链接 (前端)</h4>
+      <p>在您的应用中,检测到用户未登录时,直接跳转到以下地址:</p>
+      
+      <div class="code-block">
+          <pre>
+# PC 端
+{{ currentOrigin }}/login?app_id=YOUR_APP_ID
+
+# 移动端 (H5)
+{{ currentOrigin }}/mobile/login?app_id=YOUR_APP_ID
+          </pre>
+      </div>
+       <el-alert title="提示" type="info" :closable="false" show-icon>
+          <div>
+              请将 <code>YOUR_APP_ID</code> 替换为您实际的应用 ID。<br>
+              支持参数名 <code>app_id</code> 或 <code>appid</code>。
+          </div>
+      </el-alert>
+
+      <h4>第三步:实现回调接口 (后端)</h4>
+      <p>用户登录成功后,浏览器会跳转到您配置的回调地址,URL 格式如下:</p>
+      <code>http://your-app.com/callback?ticket=TICKET-xxxxx</code>
+      
+      <p>您的后端需要接收 <code>ticket</code>,并调用 UAP 的验证接口换取用户信息:</p>
+      
+      <el-tabs type="border-card" class="code-tabs">
+        <el-tab-pane label="Python">
+          <div class="code-block">
+            <pre>
+import requests
+import time
+import hmac
+import hashlib
+
+def generate_signature(secret: str, params: dict) -> str:
+    # 1. 过滤掉空值和 sign 字段
+    data = {k: v for k, v in params.items() if k != "sign" and v is not None}
+    # 2. 排序 key
+    sorted_keys = sorted(data.keys())
+    # 3. 拼接字符串
+    query_string = "&".join([f"{k}={data[k]}" for k in sorted_keys])
+    # 4. 计算 HMAC-SHA256
+    signature = hmac.new(
+        secret.encode('utf-8'),
+        query_string.encode('utf-8'),
+        hashlib.sha256
+    ).hexdigest()
+    return signature
+
+def validate_ticket(ticket):
+    # API 地址通常为 /api/v1/simple/validate
+    url = "http://your-uap-domain/api/v1/simple/validate"
+    app_secret = "YOUR_APP_SECRET" # 务必保密
+
+    payload = {
+        "app_id": "YOUR_APP_ID",
+        "ticket": ticket,
+        "timestamp": int(time.time())
+    }
+    # 使用您的 App Secret 计算签名
+    payload["sign"] = generate_signature(app_secret, payload)
+    
+    try:
+        resp = requests.post(url, json=payload)
+        return resp.json()
+    except Exception as e:
+        print("Error:", e)
+        return None
+            </pre>
+          </div>
+        </el-tab-pane>
+
+        <el-tab-pane label="Java">
+          <div class="code-block">
+            <pre>
+// 依赖建议: OkHttp 或 Apache HttpClient, FastJson/Jackson
+import okhttp3.*;
+import com.alibaba.fastjson.JSON;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+public class TicketValidator {
+    public static final String APP_ID = "YOUR_APP_ID";
+    public static final String APP_SECRET = "YOUR_APP_SECRET";
+    public static final String API_URL = "http://your-uap-domain/api/v1/simple/validate";
+
+    public static String generateSign(Map&lt;String, String&gt; params, String secret) {
+        try {
+            List&lt;String&gt; sortedKeys = new ArrayList&lt;&gt;(params.keySet());
+            Collections.sort(sortedKeys);
+            
+            StringBuilder sb = new StringBuilder();
+            for (String key : sortedKeys) {
+                if (!key.equals("sign") && params.get(key) != null) {
+                    if (sb.length() > 0) sb.append("&");
+                    sb.append(key).append("=").append(params.get(key));
+                }
+            }
+            
+            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
+            SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
+            sha256_HMAC.init(secret_key);
+            
+            byte[] bytes = sha256_HMAC.doFinal(sb.toString().getBytes(StandardCharsets.UTF_8));
+            StringBuilder hex = new StringBuilder();
+            for (byte b : bytes) hex.append(String.format("%02x", b));
+            return hex.toString();
+        } catch (Exception e) {
+            return "";
+        }
+    }
+
+    public static void validate(String ticket) {
+        try {
+            long timestamp = System.currentTimeMillis() / 1000;
+            
+            Map&lt;String, String&gt; params = new HashMap&lt;&gt;();
+            params.put("app_id", APP_ID);
+            params.put("ticket", ticket);
+            params.put("timestamp", String.valueOf(timestamp));
+            
+            String sign = generateSign(params, APP_SECRET); 
+            params.put("sign", sign);
+            
+            OkHttpClient client = new OkHttpClient();
+            RequestBody body = RequestBody.create(
+                MediaType.parse("application/json; charset=utf-8"), 
+                JSON.toJSONString(params)
+            );
+            
+            Request request = new Request.Builder()
+                .url(API_URL)
+                .post(body)
+                .build();
+                
+            Response response = client.newCall(request).execute();
+            System.out.println(response.body().string());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}
+            </pre>
+          </div>
+        </el-tab-pane>
+
+        <el-tab-pane label="Kotlin">
+          <div class="code-block">
+            <pre>
+import okhttp3.*
+import com.google.gson.Gson
+import javax.crypto.Mac
+import javax.crypto.spec.SecretKeySpec
+import java.io.IOException
+import java.util.*
+
+object AuthUtils {
+    fun generateSign(params: Map&lt;String, String&gt;, secret: String): String {
+        val sortedKeys = params.keys.filter { it != "sign" }.sorted()
+        val queryString = sortedKeys.joinToString("&") { "${it}=${params[it]}" }
+        
+        val hmacSha256 = "HmacSHA256"
+        val secretKeySpec = SecretKeySpec(secret.toByteArray(Charsets.UTF_8), hmacSha256)
+        val mac = Mac.getInstance(hmacSha256)
+        mac.init(secretKeySpec)
+        
+        return mac.doFinal(queryString.toByteArray(Charsets.UTF_8))
+            .joinToString("") { "%02x".format(it) }
+    }
+}
+
+fun validateTicket(ticket: String) {
+    val appId = "YOUR_APP_ID"
+    val appSecret = "YOUR_APP_SECRET"
+    val url = "http://your-uap-domain/api/v1/simple/validate"
+    
+    val timestamp = System.currentTimeMillis() / 1000
+    val params = mutableMapOf(
+        "app_id" to appId,
+        "ticket" to ticket,
+        "timestamp" to timestamp.toString()
+    )
+    
+    params["sign"] = AuthUtils.generateSign(params, appSecret)
+    
+    val client = OkHttpClient()
+    val jsonBody = Gson().toJson(params)
+    val body = RequestBody.create(MediaType.parse("application/json"), jsonBody)
+    
+    val request = Request.Builder()
+        .url(url)
+        .post(body)
+        .build()
+        
+    client.newCall(request).enqueue(object : Callback {
+        override fun onFailure(call: Call, e: IOException) {
+            e.printStackTrace()
+        }
+
+        override fun onResponse(call: Call, response: Response) {
+            println(response.body()?.string())
+        }
+    })
+}
+            </pre>
+          </div>
+        </el-tab-pane>
+        
+        <el-tab-pane label="JavaScript (Node.js)">
+          <div class="code-block">
+            <pre>
+const axios = require('axios');
+const crypto = require('crypto');
+
+const APP_SECRET = 'YOUR_APP_SECRET'; // 务必保密
+
+function getSign(params) {
+  const keys = Object.keys(params)
+    .filter(k => k !== 'sign' && params[k] !== undefined)
+    .sort();
+  const queryString = keys.map(k => `${k}=${params[k]}`).join('&');
+  return crypto.createHmac('sha256', APP_SECRET)
+    .update(queryString)
+    .digest('hex');
+}
+
+async function validateTicket(ticket) {
+  const url = 'http://your-uap-domain/api/v1/simple/validate';
+  const payload = {
+    app_id: 'YOUR_APP_ID',
+    ticket: ticket,
+    timestamp: Math.floor(Date.now() / 1000)
+  };
+  
+  // 计算签名
+  payload.sign = getSign(payload);
+  
+  try {
+    const res = await axios.post(url, payload);
+    console.log('Validation Result:', res.data);
+    return res.data;
+  } catch (error) {
+    console.error('Validation Failed:', error.response?.data || error.message);
+  }
+}
+            </pre>
+          </div>
+        </el-tab-pane>
+        
+        <el-tab-pane label="Go">
+          <div class="code-block">
+            <pre>
+package main
+
+import (
+    "bytes"
+    "crypto/hmac"
+    "crypto/sha256"
+    "encoding/hex"
+    "encoding/json"
+    "fmt"
+    "net/http"
+    "sort"
+    "strings"
+    "time"
+)
+
+func GetSign(secret string, params map[string]interface{}) string {
+    var keys []string
+    for k := range params {
+        if k != "sign" {
+            keys = append(keys, k)
+        }
+    }
+    sort.Strings(keys)
+    var parts []string
+    for _, k := range keys {
+        val := fmt.Sprintf("%v", params[k])
+        parts = append(parts, fmt.Sprintf("%s=%s", k, val))
+    }
+    query := strings.Join(parts, "&")
+    h := hmac.New(sha256.New, []byte(secret))
+    h.Write([]byte(query))
+    return hex.EncodeToString(h.Sum(nil))
+}
+
+func ValidateTicket(ticket string) {
+    appId := "YOUR_APP_ID"
+    appSecret := "YOUR_APP_SECRET" 
+    apiUrl := "http://your-uap-domain/api/v1/simple/validate"
+    
+    params := map[string]interface{}{
+        "app_id":    appId,
+        "ticket":    ticket,
+        "timestamp": time.Now().Unix(),
+    }
+    
+    // 计算签名
+    params["sign"] = GetSign(appSecret, params)
+    
+    jsonData, _ := json.Marshal(params)
+    resp, err := http.Post(apiUrl, "application/json", bytes.NewBuffer(jsonData))
+    if err != nil {
+        fmt.Println("Error:", err)
+        return
+    }
+    defer resp.Body.Close()
+    
+    // 读取响应...
+    var result map[string]interface{}
+    json.NewDecoder(resp.Body).Decode(&result)
+    fmt.Println("Result:", result)
+}
+            </pre>
+          </div>
+        </el-tab-pane>
+        
+        <el-tab-pane label="Swift">
+          <div class="code-block">
+            <pre>
+import Foundation
+import CommonCrypto
+
+// 注意:需引入 CommonCrypto
+
+func hmac(string: String, key: String) -> String {
+    var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
+    CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), key, key.count, string, string.count, &digest)
+    let data = Data(digest)
+    return data.map { String(format: "%02hhx", $0) }.joined()
+}
+
+func generateSign(secret: String, params: [String: Any]) -> String {
+    let sortedKeys = params.keys.filter { $0 != "sign" }.sorted()
+    let queryParts = sortedKeys.map { key in
+        return "\(key)=\(params[key]!)"
+    }
+    let queryString = queryParts.joined(separator: "&")
+    return hmac(string: queryString, key: secret)
+}
+
+func validateTicket(ticket: String) {
+    let appId = "YOUR_APP_ID"
+    let secret = "YOUR_APP_SECRET"
+    let url = URL(string: "http://your-uap-domain/api/v1/simple/validate")!
+    
+    var request = URLRequest(url: url)
+    request.httpMethod = "POST"
+    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
+    
+    let timestamp = Int(Date().timeIntervalSince1970)
+    var params: [String: Any] = [
+        "app_id": appId,
+        "ticket": ticket,
+        "timestamp": timestamp
+    ]
+    
+    params["sign"] = generateSign(secret: secret, params: params)
+    
+    request.httpBody = try? JSONSerialization.data(withJSONObject: params)
+    
+    let task = URLSession.shared.dataTask(with: request) { data, response, error in
+        if let data = data {
+            if let json = try? JSONSerialization.jsonObject(with: data, options: []) {
+                print(json)
+            }
+        }
+    }
+    task.resume()
+}
+            </pre>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+      
+       <p><strong>响应成功 (示例):</strong></p>
+       <div class="code-block">
+        <pre>
+{
+  "valid": true,
+  "user_id": 1001,
+  "mobile": "13800138000",
+  "mapped_key": "user_zhangsan",
+  "mapped_email": "zhangsan@example.com"
+}
+        </pre>
+      </div>
+      <p>拿到用户信息后,您就可以在您的系统中创建会话 (Session/Token),完成登录!</p>
+    </div>
+    
+     <div class="section">
+      <h3>3. 常见问题</h3>
+       <ul>
+          <li><strong>问:用户已经在其他应用登录了,还需要输密码吗?</strong><br>
+          答:不需要。只要用户在 UAP 平台有有效会话,系统会自动完成认证并直接跳转回回调地址 (SSO)。</li>
+          <li><strong>问:为什么跳转回来没有带 ticket?</strong><br>
+          答:请检查应用配置的“回调地址”是否与实际跳转的地址完全一致 (包括 http/https)。</li>
+       </ul>
+     </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { Download } from '@element-plus/icons-vue'
+import { useHelpDocs } from '../../composables/useHelpDocs'
+
+const { downloadDoc } = useHelpDocs()
+const currentOrigin = window.location.origin
+</script>
+
+<style scoped>
+@import './help.css';
+</style>

+ 61 - 0
frontend/src/views/help/IntegrationOverview.vue

@@ -0,0 +1,61 @@
+<template>
+  <div class="help-content">
+    <div class="content-header">
+      <h2>平台对接概述与集成逻辑</h2>
+    </div>
+    
+    <p class="intro">
+      本节帮助开发者快速梳理统一认证平台与业务系统的对接流程,包括应用创建、用户同步策略以及账号映射规则。
+    </p>
+
+    <div class="section">
+      <h3>1. 核心对接流程</h3>
+      <ol>
+        <li><strong>创建应用:</strong> 首先需要在本平台创建一个应用,获取 App ID 和 Secret。</li>
+        <li><strong>用户同步(初始化):</strong> 
+          <ul>
+            <li>将现有业务系统的用户数据同步到本平台(映射表),可以省去后续逐个初始化的烦恼。</li>
+            <li>建议在系统上线前进行一次全量同步。</li>
+          </ul>
+        </li>
+        <li><strong>统一认证登录与账号自动创建:</strong>
+          <ul>
+            <li>当用户通过统一认证平台登录您的业务系统时,如果您的系统中尚无该用户信息(例如新注册用户):
+              <p>您可以直接在业务系统中创建一个无权限的初始账号,完成“自动注册”。</p>
+            </li>
+            <li><strong>场景示例:</strong> 用户在统一认证平台已有账号(或通过短信注册),但在您的业务平台数据库中不存在(即该平台还没有这个手机号码或映射账号数据)。
+              <ul>
+                 <li>此时,您可以通过接口获取用户信息。</li>
+                 <li>开发者可以先创建该用户(相当于同步到您的平台来)。</li>
+              </ul>
+            </li>
+          </ul>
+        </li>
+      </ol>
+    </div>
+
+    <div class="section">
+      <h3>2. 账号同步与映射规则</h3>
+      <p>在使用账号同步功能时,请注意以下关键规则:</p>
+      <ul>
+        <li><strong>手机号码(唯一标识):</strong> 用户的手机号码是统一认证平台的核心标识,<strong>同步后不能修改</strong>。</li>
+        <li><strong>可修改字段:</strong> 您只能修改用户的 <strong>映射账号</strong> (Mapped Account/ID) 和 <strong>邮箱</strong>。这允许您在业务系统账号变更时更新映射关系。</li>
+      </ul>
+    </div>
+
+    <div class="section">
+      <h3>3. 用户管理数据说明</h3>
+      <ul>
+        <li><strong>姓名与英文名:</strong> 在“用户管理”中维护的姓名和英文名称字段,<strong>不与特定应用的账号映射强行关联</strong>。</li>
+        <li><strong>设计目的:</strong> 这部分是为了方便平台数据初始化和展示,方便管理员识别用户身份,而不作为业务系统的强校验字段。</li>
+      </ul>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+</script>
+
+<style scoped>
+@import './help.css';
+</style>

+ 236 - 0
frontend/src/views/help/MessageIntegration.vue

@@ -0,0 +1,236 @@
+<template>
+  <div class="help-content">
+    <div class="content-header">
+      <h2>消息中心对接指南</h2>
+    </div>
+    <p class="intro">统一消息中心支持应用向用户发送系统通知,并支持用户通过 WebSocket 实时接收消息。本指南将指导您如何接入消息发送与推送服务。</p>
+
+    <div class="section">
+      <h3>1. 核心概念</h3>
+      <ul>
+        <li><strong>Message (私信)</strong>: 用户与用户之间的点对点消息。</li>
+        <li><strong>Notification (通知)</strong>: 系统或应用发送给用户的业务提醒,支持 SSO 跳转。</li>
+        <li><strong>WebSocket</strong>: 客户端通过长连接实时接收推送。</li>
+      </ul>
+    </div>
+
+    <div class="section">
+      <h3>2. 用户登录认证 (Auth)</h3>
+      <p>在对接消息中心之前,客户端(如 WebSocket)通常需要获取用户的访问令牌 (Token)。</p>
+      
+      <h4>2.1 用户登录 (OAuth2 表单)</h4>
+      <p>标准 OAuth2 密码模式登录,适用于 Postman 或支持 OAuth2 的客户端。</p>
+      <ul>
+        <li><strong>接口地址</strong>: <code>POST /api/v1/auth/login</code></li>
+        <li><strong>Content-Type</strong>: <code>application/x-www-form-urlencoded</code></li>
+      </ul>
+      <p><strong>请求参数 (Form Data):</strong></p>
+      <table class="param-table">
+        <thead>
+          <tr><th>字段</th><th>必填</th><th>说明</th></tr>
+        </thead>
+        <tbody>
+          <tr><td><code>username</code></td><td>是</td><td>用户手机号</td></tr>
+          <tr><td><code>password</code></td><td>是</td><td>用户密码</td></tr>
+        </tbody>
+      </table>
+      
+      <p><strong>响应示例 (JSON):</strong></p>
+      <div class="code-block">
+        <pre>
+{
+  "access_token": "eyJhbGciOiJIUzI1NiIsInR...",
+  "token_type": "bearer"
+}
+        </pre>
+      </div>
+
+      <h4>2.2 用户登录 (JSON)</h4>
+      <p>适用于前端 SPA 或移动端应用调用的 JSON 格式登录接口。</p>
+      <ul>
+        <li><strong>接口地址</strong>: <code>POST /api/v1/auth/login/json</code></li>
+        <li><strong>Content-Type</strong>: <code>application/json</code></li>
+      </ul>
+      <p><strong>请求参数 (JSON):</strong></p>
+      <div class="code-block">
+        <pre>
+{
+  "mobile": "13800138000",
+  "password": "your_password",
+  "remember_me": false // 可选,默认为 false
+}
+        </pre>
+      </div>
+      
+      <p><strong>响应示例 (JSON):</strong></p>
+      <div class="code-block">
+        <pre>
+{
+  "access_token": "eyJhbGciOiJIUzI1NiIsInR...",
+  "token_type": "bearer"
+}
+        </pre>
+      </div>
+    </div>
+
+    <div class="section">
+      <h3>3. 消息发送接口 (HTTP)</h3>
+      <p>应用端通过 HTTP 接口向指定用户发送消息。</p>
+      
+      <ul>
+        <li><strong>接口地址</strong>: <code>POST /api/v1/messages/</code></li>
+        <li><strong>认证方式</strong>:
+          <ul>
+            <li><strong>应用调用 (Server-to-Server)</strong>: 使用应用签名头信息。</li>
+            <li><strong>用户调用 (Client-to-Server)</strong>: 使用 Bearer Token。</li>
+          </ul>
+        </li>
+      </ul>
+
+      <h4>3.1 应用调用示例 (签名认证)</h4>
+      <p>适用于业务系统后端向用户推送通知。签名生成规则请参考 <a href="#">API 安全规范</a> (简单来说:<code>sign = HMAC-SHA256(secret, app_id=101&timestamp=1700000000)</code>)。</p>
+      
+      <p><strong>完整 HTTP 请求示例:</strong></p>
+      <div class="code-block">
+        <pre>
+POST /api/v1/messages/ HTTP/1.1
+Host: api.yourdomain.com
+Content-Type: application/json
+X-App-Id: 101
+X-Timestamp: 1708848000
+X-Sign: a1b2c3d4e5f6... (HMAC-SHA256签名)
+
+{
+  "app_id": 101,
+  "app_user_id": "zhangsan_oa",    // 第三方系统账号
+  "type": "NOTIFICATION",
+  "content_type": "TEXT",
+  "title": "OA审批提醒",
+  "content": "您有一条新的报销单待审批",
+  "auto_sso": true,
+  "target_url": "http://oa.com/audit/123",
+  "action_text": "立即处理"
+}
+        </pre>
+      </div>
+
+      <h4>3.2 用户调用示例 (Token 认证)</h4>
+      <p>适用于用户在前端直接发送私信(如用户 A 发送给用户 B)。</p>
+      
+      <p><strong>完整 HTTP 请求示例:</strong></p>
+      <div class="code-block">
+        <pre>
+POST /api/v1/messages/ HTTP/1.1
+Host: api.yourdomain.com
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR...
+
+{
+  "app_id": 101,
+  "receiver_id": 2048,             // 接收用户 ID
+  "type": "MESSAGE",               // 私信
+  "content_type": "TEXT",
+  "content": "你好,请问这个流程怎么走?"
+}
+        </pre>
+      </div>
+    </div>
+
+    <div class="section">
+      <h3>4. WebSocket 实时接入</h3>
+      <p>前端客户端通过 WebSocket 连接接收实时推送。</p>
+      
+      <ul>
+        <li><strong>连接地址</strong>: <code>ws://YOUR_DOMAIN/api/v1/ws/messages?token=JWT_TOKEN</code></li>
+        <li><strong>心跳机制</strong>: 客户端发送 ping,服务端回复 pong。</li>
+      </ul>
+
+      <p><strong>推送消息格式 (Server -> Client):</strong></p>
+      <div class="code-block">
+        <pre>
+{
+  "type": "NEW_MESSAGE",
+  "data": {
+    "id": 1024,
+    "type": "NOTIFICATION",
+    "title": "OA审批提醒",
+    "content": "您有一条新的报销单待审批",
+    "action_url": "http://api.com/sso/jump?app_id=101&redirect_to=...", 
+    "created_at": "2026-02-25T10:00:00"
+  }
+}
+        </pre>
+      </div>
+    </div>
+
+    <div class="section">
+      <h3>5. 调用示例 (Python)</h3>
+      <p>以下示例展示如何使用 Python 发送通知。</p>
+      <div class="code-block">
+        <pre>
+import requests
+import time
+import hmac
+import hashlib
+import json
+
+# 配置
+API_URL = "http://localhost:8000/api/v1/messages/"
+APP_ID = "101"
+APP_SECRET = "your_app_secret"
+
+# 1. 构造消息体
+payload = {
+    "app_id": int(APP_ID),
+    "app_user_id": "zhangsan_oa", # 指定第三方账号
+    "type": "NOTIFICATION",
+    "title": "请假审批",
+    "content": "张三申请年假3天",
+    "auto_sso": True,
+    "target_url": "http://oa.example.com/leave/123"
+}
+
+# 2. 生成签名 Headers
+timestamp = str(int(time.time()))
+
+# 签名参数 (注意:消息接口签名仅包含 app_id 和 timestamp)
+params = {
+    "app_id": APP_ID,
+    "timestamp": timestamp
+}
+
+# 排序并拼接: app_id=101&timestamp=1700000000
+sorted_keys = sorted(params.keys())
+query_string = "&".join([f"{k}={params[k]}" for k in sorted_keys])
+
+# HMAC-SHA256
+sign = hmac.new(
+    APP_SECRET.encode('utf-8'),
+    query_string.encode('utf-8'),
+    hashlib.sha256
+).hexdigest()
+
+headers = {
+    "X-App-Id": APP_ID,
+    "X-Timestamp": timestamp,
+    "X-Sign": sign,
+    "Content-Type": "application/json"
+}
+
+# 3. 发送
+print(f"Signing string: {query_string}")
+resp = requests.post(API_URL, json=payload, headers=headers)
+print(resp.json())
+        </pre>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+// No specific logic needed for now
+</script>
+
+<style scoped>
+@import './help.css';
+</style>

+ 10 - 0
frontend/src/views/help/Tbd.vue

@@ -0,0 +1,10 @@
+<template>
+  <el-empty description="更多帮助文档敬请期待..." />
+</template>
+
+<script setup lang="ts">
+</script>
+
+<style scoped>
+@import './help.css';
+</style>

+ 121 - 0
frontend/src/views/help/TicketExchange.vue

@@ -0,0 +1,121 @@
+<template>
+  <div class="help-content">
+    <div class="content-header">
+      <h2>票据交互 (Ticket Exchange) 使用说明</h2>
+      <el-button type="primary" size="small" plain @click="downloadDoc('/docs/ticket_exchange.md', 'Ticket_Exchange_Guide.md')">
+        <el-icon style="margin-right: 5px"><Download /></el-icon>
+        下载 开发文档
+      </el-button>
+    </div>
+    <p class="intro">当用户已在 <strong>源应用 (Source App)</strong> 登录,需要无缝跳转到 <strong>目标应用 (Target App)</strong> 且实现免登录时,使用此接口。</p>
+
+    <div class="section">
+      <h3>1. 场景说明</h3>
+      <p>用户在 App A 点击 "跳转到 App B",App A 后端调用此接口获取 App B 的票据,然后将用户重定向到 App B。</p>
+      <ul>
+        <li><strong>接口地址</strong>: <code>POST /api/v1/simple/exchange</code></li>
+        <li><strong>Content-Type</strong>: <code>application/json</code></li>
+      </ul>
+    </div>
+
+    <div class="section">
+      <h3>2. 请求参数 (JSON Body)</h3>
+      <table class="param-table">
+        <thead>
+          <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>
+        </thead>
+        <tbody>
+          <tr><td><code>app_id</code></td><td>string</td><td>是</td><td><strong>源应用</strong> ID (发起方)</td></tr>
+          <tr><td><code>target_app_id</code></td><td>string</td><td>是</td><td><strong>目标应用</strong> ID (跳转方)</td></tr>
+          <tr><td><code>user_mobile</code></td><td>string</td><td>是</td><td>用户手机号 (作为用户身份标识)</td></tr>
+          <tr><td><code>timestamp</code></td><td>int</td><td>是</td><td>当前时间戳</td></tr>
+          <tr><td><code>sign</code></td><td>string</td><td>是</td><td>签名 (<strong>使用源应用的 Secret 计算</strong>)</td></tr>
+        </tbody>
+      </table>
+
+      <p><strong>请求示例:</strong></p>
+      <div class="code-block">
+        <pre>
+{
+  "app_id": "source_app_A",
+  "target_app_id": "target_app_B",
+  "user_mobile": "13800138000",
+  "timestamp": 1709876545,
+  "sign": "generated_signature_hex"
+}
+        </pre>
+      </div>
+    </div>
+
+    <div class="section">
+      <h3>3. 响应说明</h3>
+      <p><strong>响应成功 (200 OK):</strong></p>
+      <div class="code-block">
+        <pre>
+{
+  "ticket": "TICKET-for-target-app-...",
+  "redirect_url": "http://target-app-b.com/sso/callback?ticket=TICKET-..."
+}
+        </pre>
+      </div>
+      <ul>
+        <li><code>ticket</code>: 专门为目标应用生成的登录票据。</li>
+        <li><code>redirect_url</code>: 拼接了 ticket 的目标应用重定向地址(自动取自目标应用配置的 <code>redirect_uris</code> 的第一个地址)。</li>
+      </ul>
+
+      <p><strong>响应失败:</strong></p>
+      <ul>
+        <li><code>404 Not Found</code>: 源应用/目标应用未找到,或用户在平台中不存在。</li>
+        <li><code>400 Bad Request</code>: 签名无效。</li>
+      </ul>
+    </div>
+
+    <div class="section">
+      <h3>4. 完整交互流程</h3>
+      <div class="flow-chart">
+        <div class="actor-row">
+          <div class="actor">源应用 (Source)</div>
+          <div class="actor">统一认证 (UAP)</div>
+          <div class="actor">目标应用 (Target)</div>
+        </div>
+        
+        <div class="step">
+          <div class="arrow user-to-client">1. POST /exchange</div>
+        </div>
+        <div class="step">
+          <div class="arrow client-to-user dashed">2. 返回 redirect_url</div>
+        </div>
+        <div class="step">
+          <div class="self-action" style="margin-left: 16%;">3. 重定向用户浏览器</div>
+        </div>
+        <div class="step">
+          <div class="arrow client-to-uap dashed" style="opacity: 0.5;">4. (跳转) GET URL</div>
+        </div>
+         <div class="step">
+          <div class="arrow uap-to-client">5. POST /validate (目标应用验证)</div>
+        </div>
+        <div class="step">
+          <div class="arrow client-to-uap dashed">6. 返回用户信息</div>
+        </div>
+      </div>
+      <ol>
+        <li><strong>发起请求</strong>:源应用后端调用 <code>exchange</code> 接口,使用<strong>源应用密钥</strong>签名。</li>
+        <li><strong>获取跳转地址</strong>:接口返回包含 Ticket 的 <code>redirect_url</code>。</li>
+        <li><strong>重定向用户</strong>:源应用前端将用户浏览器重定向到该 <code>redirect_url</code>。</li>
+        <li><strong>目标应用接收</strong>:目标应用接收到请求,从 URL 参数中提取 <code>ticket</code>。</li>
+        <li><strong>验证票据</strong>:目标应用后端调用 <code>/api/v1/simple/validate</code> 接口(注意:此时 <code>app_id</code> 为目标应用 ID,<code>sign</code> 使用<strong>目标应用密钥</strong>)来验证票据并获取用户信息。</li>
+      </ol>
+    </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>

+ 92 - 0
frontend/src/views/help/UserSyncPull.vue

@@ -0,0 +1,92 @@
+<template>
+  <div class="help-content">
+    <div class="content-header">
+      <h2>全量用户同步 (Pull) 使用说明</h2>
+      <el-button type="primary" size="small" plain @click="downloadDoc('/docs/user_sync_pull.md', 'User_Sync_Pull_Guide.md')">
+        <el-icon style="margin-right: 5px"><Download /></el-icon>
+        下载 开发文档
+      </el-button>
+    </div>
+    <p class="intro">此接口用于开发者从本平台<strong>拉取全量用户数据</strong>(仅包含手机号、姓名、英文名)。<br>
+    适用场景:在业务系统初始化阶段,需要将统一认证平台的所有用户同步到本地数据库。</p>
+
+    <div class="section">
+      <h3>1. 接口概述</h3>
+      <ul>
+        <li><strong>接口地址</strong>: <code>GET /api/v1/apps/mapping/users</code></li>
+        <li><strong>认证方式</strong>: 请求头需包含 <code>X-App-Access-Token</code>(可在应用详情页查看)。</li>
+        <li><strong>接口特点</strong>: 支持分页查询,只返回基础身份信息。</li>
+      </ul>
+    </div>
+
+    <div class="section">
+      <h3>2. 请求参数 (Query Params)</h3>
+      <table class="param-table">
+        <thead>
+          <tr><th>字段</th><th>类型</th><th>必填</th><th>默认值</th><th>说明</th></tr>
+        </thead>
+        <tbody>
+          <tr><td><code>skip</code></td><td>integer</td><td><span class="tag-optional">否</span></td><td>0</td><td>偏移量(从第几条开始)</td></tr>
+          <tr><td><code>limit</code></td><td>integer</td><td><span class="tag-optional">否</span></td><td>100</td><td>每页返回数量</td></tr>
+        </tbody>
+      </table>
+
+      <p><strong>请求示例 (Curl):</strong></p>
+      <div class="code-block">
+        <pre>
+curl -X GET "http://your-uap-domain/api/v1/apps/mapping/users?skip=0&limit=100" \
+     -H "X-App-Access-Token: YOUR_APP_ACCESS_TOKEN"
+        </pre>
+      </div>
+    </div>
+
+    <div class="section">
+      <h3>3. 响应说明</h3>
+      <p><strong>响应成功 (200 OK):</strong></p>
+      <div class="code-block">
+        <pre>
+{
+  "total": 1250,
+  "items": [
+    {
+      "mobile": "13800138000",
+      "name": "张三",
+      "english_name": "zhangsan"
+    },
+    {
+      "mobile": "13900139000",
+      "name": "李四",
+      "english_name": "lisi"
+    },
+    // ... 更多用户
+  ]
+}
+        </pre>
+      </div>
+      <ul>
+        <li><code>total</code>: 系统中未删除用户的总数。</li>
+        <li><code>items</code>: 当前页的用户列表。</li>
+      </ul>
+
+      <p><strong>响应失败:</strong></p>
+      <ul>
+        <li><code>403 Forbidden</code>: Access Token 无效、过期或无权限。</li>
+      </ul>
+    </div>
+    
+    <el-alert title="开发建议" type="success" :closable="false" show-icon class="alert-box">
+      <div>建议使用循环分页拉取(例如每次 100 条),直到获取的数据条数小于 limit 或总数达到 total,以避免单次请求超时。</div>
+    </el-alert>
+  </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>

+ 388 - 0
frontend/src/views/help/help.css

@@ -0,0 +1,388 @@
+.content-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-top: 20px;
+  margin-bottom: 20px;
+}
+
+.content-header h2 {
+  margin: 0;
+  border: none;
+  padding: 0;
+}
+
+h2 {
+  font-size: 22px;
+  margin-top: 20px;
+  margin-bottom: 20px;
+  color: #1f2f3d;
+  font-weight: 600;
+  border-left: 4px solid #409eff;
+  padding-left: 10px;
+}
+
+h3 {
+  font-size: 18px;
+  margin-top: 25px;
+  margin-bottom: 15px;
+  color: #303133;
+  font-weight: 600;
+}
+
+h4 {
+  font-size: 16px;
+  margin-top: 20px;
+  margin-bottom: 10px;
+  color: #303133;
+  font-weight: bold;
+}
+
+p {
+  font-size: 15px;
+  line-height: 1.7;
+  color: #5e6d82;
+  margin-bottom: 15px;
+}
+
+ul, ol {
+  padding-left: 25px;
+  margin-bottom: 15px;
+}
+
+li {
+  font-size: 15px;
+  line-height: 1.7;
+  color: #5e6d82;
+  margin-bottom: 8px;
+}
+
+.intro {
+  font-size: 16px;
+  color: #606266;
+  background-color: #f4f4f5;
+  padding: 15px;
+  border-radius: 4px;
+  border-left: 5px solid #909399;
+}
+
+.code-block {
+  background: #282c34;
+  color: #abb2bf;
+  padding: 15px;
+  border-radius: 6px;
+  font-family: Consolas, Monaco, monospace;
+  font-size: 14px;
+  overflow-x: auto;
+  margin: 15px 0;
+  line-height: 1.5;
+}
+
+.danger {
+  color: #f56c6c;
+}
+
+.alert-box {
+  margin: 15px 0;
+}
+
+.param-table {
+  width: 100%;
+  border-collapse: collapse;
+  margin: 15px 0;
+  font-size: 14px;
+}
+
+.param-table th, .param-table td {
+  border: 1px solid #ebeef5;
+  padding: 12px;
+  text-align: left;
+}
+
+.param-table th {
+  background-color: #f5f7fa;
+  color: #606266;
+  font-weight: 600;
+}
+
+.param-table td {
+  color: #606266;
+}
+
+/* 简单的 CSS 流程图样式 */
+.flow-chart {
+  background: #fcfcfc;
+  padding: 20px;
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  margin: 20px 0;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.actor-row {
+  display: flex;
+  justify-content: space-between;
+  width: 100%;
+  max-width: 600px;
+  margin-bottom: 20px;
+  border-bottom: 1px solid #eee;
+  padding-bottom: 10px;
+}
+
+.actor {
+  font-weight: bold;
+  color: #303133;
+  width: 33%;
+  text-align: center;
+}
+
+.step {
+  width: 100%;
+  max-width: 600px;
+  display: flex;
+  margin-bottom: 15px;
+  position: relative;
+  height: 30px;
+  align-items: center;
+}
+
+.arrow {
+  height: 2px;
+  background-color: #409eff;
+  position: absolute;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 12px;
+  color: #606266;
+  white-space: nowrap;
+}
+
+.arrow::after {
+  content: '';
+  position: absolute;
+  right: 0;
+  top: -4px;
+  border-top: 5px solid transparent;
+  border-bottom: 5px solid transparent;
+  border-left: 8px solid #409eff;
+}
+
+.arrow.dashed {
+  background-color: transparent;
+  border-bottom: 2px dashed #67c23a;
+}
+.arrow.dashed::after {
+  border-left-color: #67c23a;
+}
+
+.arrow.user-to-client {
+  left: 16%;
+  width: 33%;
+  top: 15px;
+}
+
+.arrow.client-to-uap {
+  left: 50%;
+  width: 33%;
+  top: 15px;
+}
+
+.arrow.uap-to-client {
+  left: 50%;
+  width: 33%;
+  top: 15px;
+  flex-direction: row-reverse;
+}
+.arrow.uap-to-client::after {
+  left: 0;
+  right: auto;
+  border-right: 8px solid #67c23a;
+  border-left: none;
+}
+
+.arrow.client-to-user {
+  left: 16%;
+  width: 33%;
+  top: 15px;
+  flex-direction: row-reverse;
+}
+.arrow.client-to-user::after {
+  left: 0;
+  right: auto;
+  border-right: 8px solid #67c23a;
+  border-left: none;
+}
+
+.self-action {
+  background: #ecf5ff;
+  color: #409eff;
+  padding: 2px 10px;
+  border-radius: 10px;
+  font-size: 12px;
+  margin-left: 42%; 
+  border: 1px solid #d9ecff;
+}
+
+/* 特性卡片 */
+.feature-card {
+  background: #f9fafc;
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+  padding: 15px 20px;
+  margin: 15px 0;
+}
+
+.feature-card h4 {
+  margin-top: 0;
+  margin-bottom: 10px;
+  color: #409eff;
+  font-size: 15px;
+}
+
+.feature-card ul {
+  margin-bottom: 0;
+}
+
+/* 标签样式 */
+.tag-required {
+  display: inline-block;
+  padding: 2px 8px;
+  font-size: 12px;
+  background-color: #f56c6c;
+  color: white;
+  border-radius: 3px;
+  font-weight: 500;
+}
+
+.tag-optional {
+  display: inline-block;
+  padding: 2px 8px;
+  font-size: 12px;
+  background-color: #909399;
+  color: white;
+  border-radius: 3px;
+  font-weight: 500;
+}
+
+.tag-conditional {
+  display: inline-block;
+  padding: 2px 8px;
+  font-size: 12px;
+  background-color: #e6a23c;
+  color: white;
+  border-radius: 3px;
+  font-weight: 500;
+}
+
+/* 对比表格 */
+.comparison-table {
+  width: 100%;
+  border-collapse: collapse;
+  margin: 20px 0;
+  font-size: 14px;
+  box-shadow: 0 2px 4px rgba(0,0,0,0.05);
+}
+
+.comparison-table th,
+.comparison-table td {
+  border: 1px solid #ebeef5;
+  padding: 14px 12px;
+  text-align: left;
+}
+
+.comparison-table th {
+  background-color: #409eff;
+  color: white;
+  font-weight: 600;
+  text-align: center;
+}
+
+.comparison-table tbody tr:nth-child(odd) {
+  background-color: #fafafa;
+}
+
+.comparison-table tbody tr:hover {
+  background-color: #ecf5ff;
+  transition: background-color 0.3s;
+}
+
+.comparison-table .highlight {
+  color: #409eff;
+  font-weight: 600;
+}
+
+.comparison-table .success {
+  color: #67c23a;
+  font-weight: 600;
+}
+
+.comparison-table .warning {
+  color: #e6a23c;
+  font-weight: 600;
+}
+
+/* 推荐表格 */
+.recommendation-table {
+  width: 100%;
+  border-collapse: collapse;
+  margin: 20px 0;
+  font-size: 14px;
+  box-shadow: 0 2px 4px rgba(0,0,0,0.05);
+}
+
+.recommendation-table th,
+.recommendation-table td {
+  border: 1px solid #ebeef5;
+  padding: 14px 12px;
+  text-align: left;
+}
+
+.recommendation-table th {
+  background-color: #67c23a;
+  color: white;
+  font-weight: 600;
+  text-align: center;
+}
+
+.recommendation-table tbody tr:nth-child(odd) {
+  background-color: #fafafa;
+}
+
+.recommendation-table tbody tr:hover {
+  background-color: #f0f9ff;
+  transition: background-color 0.3s;
+}
+
+.recommend-badge {
+  display: inline-block;
+  padding: 4px 10px;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  border-radius: 4px;
+  font-weight: 600;
+  font-size: 13px;
+  margin: 2px;
+  white-space: nowrap;
+}
+
+/* 移动端适配 */
+@media (max-width: 600px) {
+  .flow-chart {
+    display: none; /* 手机端太窄,建议隐藏或换图 */
+  }
+  
+  .comparison-table,
+  .recommendation-table {
+    font-size: 12px;
+  }
+  
+  .comparison-table th,
+  .comparison-table td,
+  .recommendation-table th,
+  .recommendation-table td {
+    padding: 8px 6px;
+  }
+}

部分文件因文件數量過多而無法顯示