liuq 4 dagar sedan
förälder
incheckning
5d99ba72c9

+ 14 - 2
backend/app/api/v1/endpoints/apps.py

@@ -934,6 +934,7 @@ def create_mapping(
         mapped_email=mapping.mapped_email,
         user_mobile=user.mobile,
         user_status=user.status,
+        is_active=mapping.is_active,
         new_user_created=new_user_created,
         generated_password=generated_password
     )
@@ -992,11 +993,14 @@ def update_mapping(
     # Capture old values for logging
     old_key = mapping.mapped_key
     old_email = mapping.mapped_email
+    old_is_active = mapping.is_active
 
     if mapping_in.mapped_key is not None:
         mapping.mapped_key = mapping_in.mapped_key
     if mapping_in.mapped_email is not None:
         mapping.mapped_email = mapping_in.mapped_email
+    if mapping_in.is_active is not None:
+        mapping.is_active = mapping_in.is_active
 
     db.add(mapping)
     db.commit()
@@ -1011,8 +1015,16 @@ def update_mapping(
         target_user_id=mapping.user_id,
         target_mobile=mapping.user.mobile if mapping.user else None,
         details={
-            "old": {"mapped_key": old_key, "mapped_email": old_email},
-            "new": {"mapped_key": mapping.mapped_key, "mapped_email": mapping.mapped_email}
+            "old": {
+                "mapped_key": old_key,
+                "mapped_email": old_email,
+                "is_active": old_is_active,
+            },
+            "new": {
+                "mapped_key": mapping.mapped_key,
+                "mapped_email": mapping.mapped_email,
+                "is_active": mapping.is_active,
+            },
         }
     )
     

+ 2 - 2
backend/app/api/v1/endpoints/oidc.py

@@ -127,7 +127,7 @@ def get_consent_request(
                  
                  if mapping:
                      id_token_claims["preferred_username"] = mapping.mapped_key
-                     id_token_claims["email"] = mapping.mapped_key
+                     id_token_claims["email"] = mapping.mapped_email
                      logger.info(f"[SKIP] Injecting claims for user {user_id} in client {client_id}: {mapping.mapped_key}")
                  else:
                      logger.warning(f"[SKIP] No mapping found for user {user_id} in client {client_id}")
@@ -184,7 +184,7 @@ def get_consent_request(
             # We inject the mapped key (e.g. email, username) into the ID Token
             # You can standardize the claim name, e.g. "preferred_username" or "ext_id"
             id_token_claims["preferred_username"] = mapping.mapped_key
-            id_token_claims["email"] = mapping.mapped_key # If it's email
+            id_token_claims["email"] = mapping.mapped_email
             logger.info(f"Injecting claims for user {user_id} in client {client_id}: {mapping.mapped_key}")
         else:
             logger.warning(f"No mapping found for user {user_id} in client {client_id}, minimal claims injected.")

+ 1 - 0
backend/app/schemas/mapping.py

@@ -59,6 +59,7 @@ class MappingCreate(BaseModel):
 class MappingUpdate(BaseModel):
     mapped_key: Optional[str] = None
     mapped_email: Optional[str] = None
+    is_active: Optional[bool] = None
     password: str  # Required for verification
 
 class MappingDelete(BaseModel):

+ 1176 - 0
docs/LLM_UAP_INTEGRATION.md

@@ -0,0 +1,1176 @@
+# UAP 系统对接说明(面向自动化阅读)
+
+本文档**独立成篇**,描述如何在业务系统中对接 **Unified Authentication Platform(UAP,统一认证平台)**。阅读者应能仅凭本文完成:配置项填写、登录、SSO、**用户查询与映射同步**、**消息发送**(系统通知与广播)。
+
+### 示例约定
+
+下文示例中:
+
+- `https://api.hnyunzhu.com/api/v1` 表示 `UAP_API_BASE`(生产环境统一认证 API);
+- `app_demo_001`、`your_app_secret`、`eyJhbG...` 等为占位符,需替换为真实值;
+- `sign` 为按第 4 节算法计算得到的 **64 位小写十六进制**字符串,示例里用 `deadbeef...` 形式表示,**不能**直接复制使用;
+- 时间戳 `1710000000` 仅为示例,调用时需使用当前 Unix 秒。
+
+---
+
+## 1. 两类基础地址
+
+| 变量名 | 含义 | 示例 |
+|--------|------|------|
+| `UAP_API_BASE` | **HTTP(S) API 根路径**,必须包含版本前缀 `/api/v1` | `https://api.hnyunzhu.com/api/v1` |
+| `UAP_WEB_BASE` | **用户浏览器访问的前端站点根**(用于跳转登录页;本部署与 API 同主机) | `https://api.hnyunzhu.com` |
+
+下文所有接口路径均相对于 `UAP_API_BASE` 拼接,例如:`UAP_API_BASE` + `/simple/login` → `https://api.hnyunzhu.com/api/v1/simple/login`。
+
+跳转登录页使用 `UAP_WEB_BASE`:
+
+- PC:`{UAP_WEB_BASE}/login?app_id=<APP_ID>`
+- 移动端 H5:`{UAP_WEB_BASE}/mobile/login?app_id=<APP_ID>`
+
+---
+
+## 2. 对接前必填配置
+
+在 UAP 管理后台创建应用后,请收集并安全保存以下信息:
+
+| 配置项 | 说明 |
+|--------|------|
+| `APP_ID` | 应用对外标识(字符串,如 `app_xxxxxxxx`) |
+| `APP_SECRET` | 用于 **HMAC-SHA256 签名**,**仅允许在业务服务端使用**,禁止写入前端或客户端 |
+| `APP_ACCESS_TOKEN` | 应用访问令牌,用于 **M2M** 接口(HTTP Header:`X-App-Access-Token`) |
+| `REDIRECT_URIS` | 在应用中配置的回调地址列表;Redirect SSO、`sso-login`、`exchange` 等流程依赖合法回调 |
+
+可选:若使用标准 OIDC 协议,还需 OIDC 客户端凭据(本文不展开 OIDC 全流程)。
+
+### 2.1 对接方填写(凭据留空)
+
+实施对接时,请将贵司从 UAP 管理后台获取的值填入下表(可打印或复制后替换正文中的占位符)。**App Secret 仅保存在服务端,勿提交到代码仓库或前端。** 下表中 **API / 前端根地址** 已按当前生产环境填写;若贵司使用独立域名部署,请改为实际地址。
+
+| 项目 | 变量名(本文档) | 填写 |
+|------|------------------|------|
+| API 根地址 | `UAP_API_BASE` | `https://api.hnyunzhu.com/api/v1` |
+| 前端站点根地址 | `UAP_WEB_BASE` | `https://api.hnyunzhu.com` |
+| App ID | `APP_ID` |  |
+| App Secret | `APP_SECRET` |  |
+| Access Token(应用访问令牌) | `APP_ACCESS_TOKEN` |  |
+
+**说明:**
+
+- **App ID**、**App Secret**:用于 Simple Auth 签名、消息接口 `X-App-Id` / `X-Sign` 等。
+- **Access Token**:用于 HTTP Header `X-App-Access-Token`,对应 M2M(用户搜索、映射同步等);**不是** App Secret。
+- **`UAP_API_BASE`** 必须包含版本前缀 **`/api/v1`**;**`UAP_WEB_BASE`** 为浏览器访问登录页所用站点根(本部署与 API 同主机时为 `https://api.hnyunzhu.com`)。
+
+---
+
+## 3. 认证方式总览
+
+对接时会遇到**四类**身份校验,请勿混用:
+
+| 方式 | 典型场景 | 请求形式 |
+|------|----------|----------|
+| **A. JSON Body 签名(Simple Auth)** | `/simple/login`、`/simple/validate`、`/simple/exchange` 等 | Body 内含 `timestamp`、`sign`,用 `APP_SECRET` 对**除 `sign` 外**的参数做 HMAC-SHA256(见第 4 节) |
+| **B. HTTP Header 签名(应用调消息接口)** | 服务端代应用调用 `POST /messages/` | Header:`X-App-Id`、`X-Timestamp`、`X-Sign`(签名字符串仅含 `app_id` 与 `timestamp`,见第 4.2 节) |
+| **C. 用户 JWT** | 用户已登录 UAP 后调用需身份的接口(如 `POST /auth/login/json`、管理类接口等) | `Authorization: Bearer <access_token>` |
+| **D. M2M 访问令牌** | 用户搜索、映射同步、全量用户拉取 | `X-App-Access-Token: <APP_ACCESS_TOKEN>`(部分场景亦支持应用 JWT Bearer,与平台实现一致) |
+
+---
+
+## 4. 签名算法(HMAC-SHA256)
+
+平台校验逻辑与实现一致:**参数键名按 ASCII 升序排列**,拼接为 `key1=value1&key2=value2`(不含 `sign`),再对拼接串做 **HMAC-SHA256**,密钥为 UTF-8 编码的 `APP_SECRET`,结果为小写 **十六进制**字符串。
+
+**时间戳**:`timestamp` 为 Unix 秒级整数;服务端对时间偏差有约 **300 秒**容差。
+
+### 4.1 Simple Auth(JSON Body)
+
+参与签名的键为请求 JSON 中**除 `sign` 以外**的所有键(`None` 值一般应排除,与常见实现一致);**仅包含实际发送的字段**。各接口以下文「参与签名的字段」为准。
+
+**Python 示例:**
+
+```python
+import hmac
+import hashlib
+
+def sign_simple_auth(secret: str, params: dict) -> str:
+    data = {k: v for k, v in params.items() if k != "sign" and v is not None}
+    query_string = "&".join(f"{k}={data[k]}" for k in sorted(data.keys()))
+    return hmac.new(
+        secret.encode("utf-8"),
+        query_string.encode("utf-8"),
+        hashlib.sha256,
+    ).hexdigest()
+```
+
+### 4.2 消息接口(Header 签名)
+
+仅对两个键签名:`app_id`(即 `X-App-Id` 的值)、`timestamp`(即 `X-Timestamp` 的字符串值,与 Header 中一致)。拼接串为:
+
+`app_id=<APP_ID>&timestamp=<TIMESTAMP>`
+
+对该字符串做 HMAC-SHA256,结果放入 `X-Sign`。
+
+**消息 Header 签名示例(伪代码):**
+
+- 待签名字符串:`app_id=app_demo_001&timestamp=1710000000`
+- `X-Timestamp` 取值与字符串中的 `timestamp` **完全一致**(通常为数字的十进制字符串,如 `"1710000000"`)。
+
+```http
+X-App-Id: app_demo_001
+X-Timestamp: 1710000000
+X-Sign: 1a2b3c4d5e6f708192a3b4c5d6e7f8090a1b2c3d4e5f678901234567890abcd
+```
+
+---
+
+## 5. 登录相关接口
+
+### 5.1 `POST /simple/login`(密码登录)
+
+- **Content-Type**:`application/json`
+- **说明**:未提供 `app_id` 时为**平台账号登录**(返回用户 JWT);提供 `app_id` 时为**应用 SSO 登录**(返回 `ticket`)。应用侧若提供 `sign` / `timestamp`,二者须**同时**出现,且服务端会校验签名(推荐服务端调用时始终带签名)。
+
+#### 请求体字段
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `app_id` | string \| null | 否 | 不提供:平台登录;提供:应用 SSO,返回 `ticket` |
+| `identifier` | string | 是 | 用户标识:手机号、映射 `mapped_key` 或映射邮箱等(平台登录时按手机号查找) |
+| `password` | string | 是 | 登录密码 |
+| `remember_me` | boolean | 否 | 默认 `false`;为 `true` 时 JWT 有效期延长(**仅平台登录**生效) |
+| `timestamp` | int | 条件 | 与 `sign` 同时提供或同时省略;提供时参与签名 |
+| `sign` | string | 条件 | HMAC-SHA256 十六进制;**应用 SSO 时推荐必带** |
+
+**应用 SSO 且启用签名时,参与签名的字段为**:`app_id`、`identifier`、`password`、`timestamp`(不含 `remember_me`)。
+
+#### 响应 `200`:`PasswordLoginResponse`
+
+| 字段 | 类型 | 何时出现 | 说明 |
+|------|------|----------|------|
+| `ticket` | string \| null | 提供 `app_id` 且登录成功 | 临时票据,供 `POST /simple/validate` 使用 |
+| `access_token` | string \| null | **未**提供 `app_id` 且登录成功 | 用户 JWT |
+| `token_type` | string \| null | 平台登录成功 | 固定为 `bearer` |
+| `role` | string \| null | 平台登录成功 | 如 `SUPER_ADMIN`、`DEVELOPER`、`ORDINARY_USER` |
+
+应用 SSO 成功时典型返回:`{ "ticket": "<票据字符串>" }`(无 `access_token`)。
+
+#### 调用与返回示例
+
+**A. 应用 SSO(带签名,返回 `ticket`)**
+
+```http
+POST https://api.hnyunzhu.com/api/v1/simple/login HTTP/1.1
+Content-Type: application/json
+
+{
+  "app_id": "app_demo_001",
+  "identifier": "13800138000",
+  "password": "UserPassword123",
+  "timestamp": 1710000000,
+  "sign": "1a2b3c4d5e6f708192a3b4c5d6e7f8090a1b2c3d4e5f678901234567890abcd"
+}
+```
+
+`sign` 由 `APP_SECRET` 对以下键签名(不含 `sign`):`app_id`、`identifier`、`password`、`timestamp`。
+
+**响应 `200`:**
+
+```json
+{
+  "ticket": "TICKET-7f8e9d0a-1234-5678-abcd-ef0123456789"
+}
+```
+
+**B. 平台登录(无 `app_id`,返回 JWT)**
+
+```http
+POST https://api.hnyunzhu.com/api/v1/simple/login HTTP/1.1
+Content-Type: application/json
+
+{
+  "identifier": "13800138000",
+  "password": "UserPassword123",
+  "remember_me": false
+}
+```
+
+**响应 `200`:**
+
+```json
+{
+  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.xxx",
+  "token_type": "bearer",
+  "role": "ORDINARY_USER"
+}
+```
+
+**错误示例(密码错误,`401`):**
+
+```json
+{
+  "detail": "密码错误"
+}
+```
+
+#### 常见错误 HTTP 状态与 `detail`(节选)
+
+| 状态码 | 含义(示例) |
+|--------|----------------|
+| 400 | 用户已禁用、签名无效(应用侧) |
+| 401 | 密码错误 |
+| 404 | 用户未找到、应用未找到 |
+
+---
+
+### 5.2 `POST /simple/sms-login`(短信验证码登录)
+
+- **Content-Type**:`application/json`
+- **说明**:逻辑与 5.1 对称——无 `app_id` 返回平台 JWT;有 `app_id` 返回 `ticket`。签名可选;若带 `sign` 必须同时带 `timestamp`。短信功能需在平台开启(否则可能 `403`)。
+
+#### 请求体字段
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `mobile` | string | 是 | 手机号 |
+| `code` | string | 是 | 短信验证码 |
+| `remember_me` | boolean | 否 | 默认 `false`;影响平台登录 JWT 有效期 |
+| `app_id` | string \| null | 否 | 不提供:平台登录;提供:应用 SSO,返回 `ticket` |
+| `timestamp` | int | 条件 | 与 `sign` 成对出现 |
+| `sign` | string | 条件 | 应用 SSO 且启用签名时:**参与签名的字段为** `app_id`、`mobile`、`code`、`timestamp` |
+
+#### 响应 `200`
+
+与 **5.1** 相同,使用 `PasswordLoginResponse`(`ticket` 或 `access_token` + `token_type` + `role`)。
+
+#### 调用与返回示例
+
+**应用 SSO(带签名)**
+
+```http
+POST https://api.hnyunzhu.com/api/v1/simple/sms-login HTTP/1.1
+Content-Type: application/json
+
+{
+  "app_id": "app_demo_001",
+  "mobile": "13800138000",
+  "code": "123456",
+  "timestamp": 1710000000,
+  "sign": "2b3c4d5e6f708192a3b4c5d6e7f8090a1b2c3d4e5f678901234567890abcde"
+}
+```
+
+参与签名的键:`app_id`、`mobile`、`code`、`timestamp`。
+
+**响应 `200`:**
+
+```json
+{
+  "ticket": "TICKET-8a9b0c1d-2345-6789-bcde-f01234567890"
+}
+```
+
+**平台短信登录(无 `app_id`)**
+
+```json
+{
+  "mobile": "13800138000",
+  "code": "123456",
+  "remember_me": false
+}
+```
+
+**响应 `200`:**
+
+```json
+{
+  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+  "token_type": "bearer",
+  "role": "ORDINARY_USER"
+}
+```
+
+#### 常见错误(节选)
+
+| 状态码 | 含义(示例) |
+|--------|----------------|
+| 400 | 验证码错误或已过期、用户已禁用 |
+| 403 | 短信登录功能未开启 |
+| 404 | 用户未找到、应用未找到 |
+
+---
+
+### 5.3 `POST /simple/validate`(验证票据)
+
+- **Content-Type**:`application/json`
+- **认证**:必须使用 **目标应用**的 `APP_SECRET` 对 Body 签名(`sign` / `timestamp` **必填**)。
+
+#### 请求体字段
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `ticket` | string | 是 | 来自登录跳转、`exchange`、`sso-login` 等流程的票据 |
+| `app_id` | string | 是 | **消费票据的应用** ID(与为该应用签名的 Secret 一致) |
+| `timestamp` | int | 是 | Unix 秒 |
+| `sign` | string | 是 | 参与签名的字段为:`ticket`、`app_id`、`timestamp` |
+
+#### 响应 `200`:`TicketValidateResponse`
+
+**票据有效时:**
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `valid` | bool | `true` |
+| `user_id` | int | 平台用户主键 |
+| `mobile` | string | 用户手机号 |
+| `mapped_key` | string \| null | 该用户在**当前应用**下的映射账号(无映射时为 `null`) |
+| `mapped_email` | string \| null | 该用户在**当前应用**下的映射邮箱 |
+
+**票据无效或已消费:**
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `valid` | bool | `false` |
+| `user_id`、`mobile` 等 | — | 一般为 `null` 或省略(以实际 JSON 为准) |
+
+#### 调用与返回示例
+
+参与签名的键:`ticket`、`app_id`、`timestamp`(不含 `sign`)。
+
+```http
+POST https://api.hnyunzhu.com/api/v1/simple/validate HTTP/1.1
+Content-Type: application/json
+
+{
+  "app_id": "app_demo_001",
+  "ticket": "TICKET-7f8e9d0a-1234-5678-abcd-ef0123456789",
+  "timestamp": 1710000001,
+  "sign": "3c4d5e6f708192a3b4c5d6e7f8090a1b2c3d4e5f678901234567890abcdef"
+}
+```
+
+**响应 `200`(成功):**
+
+```json
+{
+  "valid": true,
+  "user_id": 1001,
+  "mobile": "13800138000",
+  "mapped_key": "ext_user_001",
+  "mapped_email": "zhangsan@example.com"
+}
+```
+
+**响应 `200`(票据无效或已使用):**
+
+```json
+{
+  "valid": false
+}
+```
+
+**错误示例(签名错误,`400`):**
+
+```json
+{
+  "detail": "签名无效"
+}
+```
+
+#### 常见错误
+
+| 状态码 | 含义(示例) |
+|--------|----------------|
+| 400 | 签名无效 |
+| 404 | 应用未找到 |
+
+---
+
+### 5.4 平台用户登录(获取 JWT,非 Simple 票据流程)
+
+#### `POST /auth/login/json`
+
+- **Content-Type**:`application/json`
+
+**请求体:**
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `mobile` | string | 是 | 用户手机号(平台账号) |
+| `password` | string | 是 | 密码 |
+| `remember_me` | boolean | 否 | 默认 `false`;`true` 时使用更长有效期 JWT |
+| `app_id` | string \| null | 否 | 可选;直接平台登录通常可省略 |
+
+**响应 `200`:**
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `access_token` | string | 用户 JWT |
+| `token_type` | string | 固定为 `bearer` |
+
+**常见错误:** `400`(手机号或密码错误、账户已禁用)、`403`(账户待审核)。
+
+#### 调用与返回示例
+
+**`POST /auth/login/json`**
+
+```http
+POST https://api.hnyunzhu.com/api/v1/auth/login/json HTTP/1.1
+Content-Type: application/json
+
+{
+  "mobile": "13800138000",
+  "password": "UserPassword123",
+  "remember_me": true
+}
+```
+
+**响应 `200`:**
+
+```json
+{
+  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+  "token_type": "bearer"
+}
+```
+
+**`POST /auth/login`(表单)**
+
+```http
+POST https://api.hnyunzhu.com/api/v1/auth/login HTTP/1.1
+Content-Type: application/x-www-form-urlencoded
+
+username=13800138000&password=UserPassword123
+```
+
+**响应 `200`:** 与 JSON 登录相同。
+
+**错误示例(`400`):**
+
+```json
+{
+  "detail": "手机号或密码错误"
+}
+```
+
+(`POST /auth/login` 表单字段与响应与上表一致,见上文 HTTP 示例。)
+
+---
+
+## 6. SSO 相关接口
+
+### 6.1 浏览器跳转登录(Redirect SSO)
+
+**非 JSON 接口**,步骤如下:
+
+1. 浏览器访问:`{UAP_WEB_BASE}/login?app_id=<APP_ID>` 或 `{UAP_WEB_BASE}/mobile/login?app_id=<APP_ID>`。
+2. 用户在 UAP 登录成功后,浏览器重定向到应用配置的**回调 URL**,查询参数携带 `ticket=<票据>`。
+3. 应用**服务端**调用 **`POST /simple/validate`**(见 5.3)换用户信息并建立会话。
+
+**前提**:回调地址必须在应用 `REDIRECT_URIS` 中配置。
+
+#### 调用与返回示例
+
+**浏览器打开登录页(仅说明 URL,无 JSON Body):**
+
+```
+https://api.hnyunzhu.com/login?app_id=app_demo_001
+```
+
+**登录成功后,浏览器跳转到应用回调(示例):**
+
+```
+https://biz.example.com/oauth/callback?ticket=TICKET-7f8e9d0a-1234-5678-abcd-ef0123456789
+```
+
+业务后端再使用 **5.3** 的 `POST /simple/validate` 换用户信息。
+
+---
+
+### 6.2 `POST /simple/sso-login`(API 获取跳转 URL)
+
+- **Content-Type**:`application/json`
+- **说明**:返回业务系统带 `ticket` 的完整 **`redirect_url`**,客户端应**导航**至该 URL。目标应用须存在;**SIMPLE_API** 应用返回 `回调地址?ticket=...`;**OIDC** 应用返回简化后的根 URL(仅 `scheme`+`netloc`),详见下表。
+
+#### 请求头(二选一)
+
+| 模式 | 请求头 / Body |
+|------|----------------|
+| 会话模式 | `Authorization: Bearer <用户 access_token>`;Body 仅需目标应用 `app_id` |
+| 凭据模式 | 无 Bearer;Body 须含 `app_id`、`username`、`password` |
+
+#### 请求体字段
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `app_id` | string | 是 | 要进入的**目标应用** ID |
+| `username` | string | 条件 | 未带有效 Bearer 时**必填**:手机号、映射 key 或映射邮箱等 |
+| `password` | string | 条件 | 未带有效 Bearer 时**必填** |
+
+#### 响应 `200`:`SsoLoginResponse`
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `redirect_url` | string | **SIMPLE_API**:`{首个 redirect_uri}?ticket=<票据>`;**OIDC**:回调 URI 解析后的 `scheme://host[:port]`(无路径) |
+
+#### 常见错误
+
+| 状态码 | 含义(示例) |
+|--------|----------------|
+| 400 | 应用未找到、未配置重定向 URI、用户已禁用、协议不支持等 |
+| 401 | 认证失败 |
+
+#### 调用与返回示例
+
+**会话模式(已持有用户 JWT)**
+
+```http
+POST https://api.hnyunzhu.com/api/v1/simple/sso-login HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
+
+{
+  "app_id": "target_app_002"
+}
+```
+
+**响应 `200`(SIMPLE_API 应用):**
+
+```json
+{
+  "redirect_url": "https://biz.example.com/callback?ticket=TICKET-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
+}
+```
+
+**凭据模式(未登录)**
+
+```http
+POST https://api.hnyunzhu.com/api/v1/simple/sso-login HTTP/1.1
+Content-Type: application/json
+
+{
+  "app_id": "target_app_002",
+  "username": "13800138000",
+  "password": "UserPassword123"
+}
+```
+
+**响应 `200`:** 同上,为带 `ticket` 的 `redirect_url`。
+
+---
+
+### 6.3 `POST /simple/exchange`(源应用用户免登进目标应用)
+
+- **Content-Type**:`application/json`
+- **签名**:使用**源应用** `APP_SECRET`;`sign` / `timestamp` **必填**。
+
+#### 请求体字段
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `app_id` | string | 是 | **源应用** ID |
+| `target_app_id` | string | 是 | **目标应用** ID |
+| `user_mobile` | string | 是 | 用户在 UAP 的手机号(须已存在) |
+| `timestamp` | int | 是 | Unix 秒 |
+| `sign` | string | 是 | 参与签名:`app_id`、`target_app_id`、`user_mobile`、`timestamp` |
+
+#### 响应 `200`:`TicketExchangeResponse`
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `ticket` | string | 目标应用可用的票据 |
+| `redirect_url` | string | 目标应用首个 `redirect_uri` 拼接 `?ticket=<ticket>` 的完整 URL(若未配置合法 URI,实现中可能回退占位,生产环境应保证配置正确) |
+
+#### 常见错误
+
+| 状态码 | 含义(示例) |
+|--------|----------------|
+| 400 | 签名无效 |
+| 404 | 源应用 / 目标应用 / 用户未找到 |
+
+目标应用收到 `ticket` 后,用**目标应用**的 `app_id` + `APP_SECRET` 调用 **`POST /simple/validate`**。
+
+#### 调用与返回示例
+
+参与签名的键:`app_id`、`target_app_id`、`user_mobile`、`timestamp`(使用**源应用** Secret)。
+
+```http
+POST https://api.hnyunzhu.com/api/v1/simple/exchange HTTP/1.1
+Content-Type: application/json
+
+{
+  "app_id": "source_app_001",
+  "target_app_id": "target_app_002",
+  "user_mobile": "13800138000",
+  "timestamp": 1710000002,
+  "sign": "4d5e6f708192a3b4c5d6e7f8090a1b2c3d4e5f678901234567890abcdef01"
+}
+```
+
+**响应 `200`:**
+
+```json
+{
+  "ticket": "TICKET-bbbbcccc-dddd-eeee-ffff-000011112222",
+  "redirect_url": "https://target.example.com/callback?ticket=TICKET-bbbbcccc-dddd-eeee-ffff-000011112222"
+}
+```
+
+---
+
+### 6.4 `GET /simple/sso/jump`(通知内嵌 SSO 跳转)
+
+用于消息中配置的跳转:用户点击后,若已在 UAP 登录则带上 Ticket 重定向到应用回调。
+
+#### 查询参数
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `app_id` | string | 是 | 目标应用 ID |
+| `redirect_to` | string | 是 | 登录成功后最终要到达的业务页 URL(经 URL 编码传入) |
+
+#### 响应行为(非 JSON)
+
+| 场景 | HTTP | 说明 |
+|------|------|------|
+| 当前**未**登录 UAP | `307`/`302` 等 | `RedirectResponse` 到相对路径 `/login?redirect=<当前完整 jump URL>`(依赖网关/前端将用户导向登录页) |
+| 当前**已**登录 | `307`/`302` 等 | 重定向到应用 `redirect_uris` 中首个 URI,查询参数包含 `ticket`、`next`(`next` 为 `redirect_to`) |
+| 应用不存在 | 404 | `detail` 说明 |
+| 未配置回调 | 400 | `detail` 说明 |
+
+#### 调用与返回示例
+
+**请求 URL(`redirect_to` 需 URL 编码):**
+
+```
+GET https://api.hnyunzhu.com/api/v1/simple/sso/jump?app_id=app_demo_001&redirect_to=https%3A%2F%2Foa.example.com%2Fapprove%2F123
+```
+
+**已登录时:** HTTP 重定向(`302`/`307`),`Location` 类似:
+
+```
+https://biz.example.com/callback?ticket=TICKET-xxx&next=https%3A%2F%2Foa.example.com%2Fapprove%2F123
+```
+
+**未登录时:** 重定向到登录页,`Location` 可能类似:
+
+```
+/login?redirect=https%3A%2F%2Fapi.hnyunzhu.com%2Fapi%2Fv1%2Fsimple%2Fsso%2Fjump%3Fapp_id%3D...
+```
+
+---
+
+## 7. 用户接口(M2M)
+
+以下接口使用 **`X-App-Access-Token: <APP_ACCESS_TOKEN>`**(或实现所支持的应用 JWT),**不使用** Simple Auth 的 Body 签名。
+
+### 7.1 推荐业务流程(查询 → 建用户 / 推映射)
+
+对接方在「把业务账号与平台用户绑定」时,建议按下面顺序操作:
+
+1. **先查询用户**  
+   调用 **`GET /users/search`**(见 **7.2**),用手机号、姓名等关键词确认平台是否**已存在**该用户,并记录返回中的 **`id`(平台用户 ID)**、`mobile`、`name` 等。  
+   - **能查到**:说明平台已有账号,只需为本应用维护**映射**(`mapped_key`、`mapped_email` 等)。  
+   - **查不到**:需先在平台**创建用户**,再推映射。创建方式包括:  
+     - 在 **UAP 管理后台**人工新增;或  
+     - 若贵司具备超级管理员能力,调用管理端 **`POST /users/`** 创建(需管理员权限);或  
+     - 直接使用 **`POST /apps/mapping/sync`** 的 **`UPSERT`**:当手机号在平台不存在时,可在同一请求中携带 **`name`** 等,由平台**新建用户并建立映射**(见 **7.3**)。
+
+2. **填充本应用账号信息并同步**  
+   使用查询到的 **`mobile`**(及必要时 **`name`**)与业务侧 **`mapped_key` / `mapped_email`**,调用 **`POST /apps/mapping/sync`**:  
+   - **新增绑定**:`sync_action` 为 **`UPSERT`**(默认),平台会插入或更新**当前应用**下的映射;若本次同时新建了平台用户,响应中 **`new_user_created`** 为 `true`。  
+   - **仅改映射**:对已存在用户再次 **`UPSERT`**,可更新 `mapped_key`、`mapped_email`、`is_active` 等;**不可**通过该接口修改已存在用户的姓名、手机号等基础档案(与接口校验一致)。  
+   - **解除映射**:`sync_action` 为 **`DELETE`**(仅删映射,不删平台用户)。
+
+3. **全量对账(可选)**  
+   需要批量拉取平台用户基础信息时,使用 **`GET /apps/mapping/users`**(见 **7.4**)。
+
+---
+
+### 7.2 `GET /users/search`(用户查询)
+
+- **说明**:按关键词搜索**已激活、未删除**的平台用户,用于在推映射前确认人选。支持 **应用访问令牌** 或用户 JWT。
+- **路径**:`GET /users/search`
+- **认证**:`X-App-Access-Token: <APP_ACCESS_TOKEN>`,或 `Authorization: Bearer <用户 JWT>`
+
+#### 查询参数
+
+| 参数 | 类型 | 必填 | 默认值 | 说明 |
+|------|------|------|--------|------|
+| `q` | string | 否 | — | 关键词;对**手机号、姓名、英文名**模糊匹配(`ilike`)。不传时返回一批活跃用户(受 `limit` 限制) |
+| `limit` | int | 否 | 20 | 最大返回条数 |
+
+**说明**:若使用**用户 JWT** 调用,结果中会**排除当前登录用户本人**;使用 **应用令牌** 时无此排除。
+
+#### 响应 `200`
+
+返回 **JSON 数组**,元素为平台用户对象,主要字段如下:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `id` | int | 平台用户 ID(可用于发消息时的 `receiver_id` 等) |
+| `mobile` | string | 手机号 |
+| `name` | string \| null | 姓名 |
+| `english_name` | string \| null | 英文名 |
+| `status` | string | 如 `ACTIVE` |
+| `role` | string \| null | 角色 |
+| `created_at` | string | 创建时间 ISO8601 |
+| `updated_at` | string | 更新时间 ISO8601 |
+| `is_deleted` | int | 是否删除标记 |
+
+#### 调用与返回示例
+
+```http
+GET https://api.hnyunzhu.com/api/v1/users/search?q=13800138000&limit=20 HTTP/1.1
+X-App-Access-Token: pA9s8d7f6g5h4j3k2l1m0n9o8p7q6r5s4t3u2v1w0
+```
+
+**响应 `200` 示例:**
+
+```json
+[
+  {
+    "id": 1001,
+    "mobile": "13800138000",
+    "name": "张三",
+    "english_name": "zhangsan",
+    "status": "ACTIVE",
+    "role": "ORDINARY_USER",
+    "created_at": "2025-01-10T08:00:00",
+    "updated_at": "2025-06-01T12:00:00",
+    "is_deleted": 0
+  }
+]
+```
+
+---
+
+### 7.3 `POST /apps/mapping/sync`
+
+- **Content-Type**:`application/json`
+
+#### 请求体字段 `UserSyncRequest`
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `mobile` | string | 是 | 中国大陆手机号,正则 `^1[3-9]\d{9}$`(与平台校验一致) |
+| `name` | string \| null | 条件 | **UPSERT 时必填**(非空姓名);已存在用户时**不可**通过此接口改姓名等基础信息;**DELETE** 可不依赖姓名逻辑(以 `sync_action` 分支为准) |
+| `english_name` | string \| null | 否 | 已废弃于 M2M;英文名由平台按规则生成 |
+| `password` | string \| null | 否 | 按平台实现(若有) |
+| `status` | string \| null | 否 | 按平台实现 |
+| `mapped_key` | string | 是 | 外部系统用户 ID,长度 1~100 |
+| `mapped_email` | string \| null | 否 | 外部邮箱 |
+| `is_active` | boolean \| null | 否 | 映射是否启用;`null` 表示不修改 |
+| `sync_action` | string \| null | 否 | `UPSERT`(默认)或 `DELETE` |
+
+#### 响应 `200`:`MappingResponse`
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `id` | int | 映射记录 ID |
+| `app_id` | int | 应用**数据库主键**(整数) |
+| `user_id` | int | 平台用户 ID |
+| `mapped_key` | string \| null | 映射账号 |
+| `mapped_email` | string \| null | 映射邮箱 |
+| `user_mobile` | string | 用户手机号 |
+| `user_status` | string | 用户统一状态描述 |
+| `is_active` | bool | 映射是否有效;DELETE 后可能为 `false` |
+| `new_user_created` | bool | 是否本次新建了平台用户 |
+| `generated_password` | string \| null | 若平台为新用户生成了初始密码,可能返回 |
+
+#### 常见错误
+
+| 状态码 | 含义(节选) |
+|--------|----------------|
+| 400 | 参数非法、与业务规则冲突(如重复映射、禁止修改字段) |
+| 403 | Token 无效 |
+
+#### 调用与返回示例
+
+**UPSERT**
+
+```http
+POST https://api.hnyunzhu.com/api/v1/apps/mapping/sync HTTP/1.1
+Content-Type: application/json
+X-App-Access-Token: pA9s8d7f6g5h4j3k2l1m0n9o8p7q6r5s4t3u2v1w0
+
+{
+  "mobile": "13800138000",
+  "name": "张三",
+  "mapped_key": "ext_user_1001",
+  "mapped_email": "zhangsan@partner.com",
+  "is_active": true,
+  "sync_action": "UPSERT"
+}
+```
+
+**响应 `200`:**
+
+```json
+{
+  "id": 501,
+  "app_id": 12,
+  "user_id": 1001,
+  "mapped_key": "ext_user_1001",
+  "mapped_email": "zhangsan@partner.com",
+  "user_mobile": "13800138000",
+  "user_status": "ACTIVE",
+  "is_active": true,
+  "new_user_created": true,
+  "generated_password": null
+}
+```
+
+**DELETE(仅删除映射)**
+
+```http
+POST https://api.hnyunzhu.com/api/v1/apps/mapping/sync HTTP/1.1
+Content-Type: application/json
+X-App-Access-Token: pA9s8d7f6g5h4j3k2l1m0n9o8p7q6r5s4t3u2v1w0
+
+{
+  "mobile": "13800138000",
+  "mapped_key": "ext_user_1001",
+  "sync_action": "DELETE"
+}
+```
+
+**响应 `200`:** 结构同 `MappingResponse`,`is_active` 可能为 `false`(见字段表)。
+
+---
+
+### 7.4 `GET /apps/mapping/users`
+
+- **认证**:`X-App-Access-Token`
+
+#### 查询参数
+
+| 参数 | 类型 | 必填 | 默认值 | 说明 |
+|------|------|------|--------|------|
+| `skip` | int | 否 | 0 | 跳过条数 |
+| `limit` | int | 否 | 100 | 每页条数 |
+
+#### 响应 `200`:`UserSyncList`
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `total` | int | 符合条件的用户总数 |
+| `items` | array | 用户列表 |
+
+**`items[]` 元素 `UserSyncSimple`:**
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `mobile` | string | 手机号 |
+| `name` | string \| null | 姓名 |
+| `english_name` | string \| null | 英文名 |
+
+#### 调用与返回示例
+
+```http
+GET https://api.hnyunzhu.com/api/v1/apps/mapping/users?skip=0&limit=100 HTTP/1.1
+X-App-Access-Token: pA9s8d7f6g5h4j3k2l1m0n9o8p7q6r5s4t3u2v1w0
+```
+
+**响应 `200`:**
+
+```json
+{
+  "total": 1250,
+  "items": [
+    {
+      "mobile": "13800138000",
+      "name": "张三",
+      "english_name": "zhangsan"
+    },
+    {
+      "mobile": "13900139000",
+      "name": "李四",
+      "english_name": "lisi"
+    }
+  ]
+}
+```
+
+---
+
+## 8. 消息发送接口(仅 `POST /messages/`)
+
+本节**仅**说明由**应用服务端**调用、使用 **Header 签名**(第 3 节方式 B、第 4.2 节)发送 **`NOTIFICATION`** 的两种形态:
+
+1. **单用户系统通知**(指定一名接收者)  
+2. **广播**(向全体活跃用户各发一条)
+
+不涉及用户间私信(`type: MESSAGE`)及用户 JWT 代发,对接方无需关注。
+
+- **路径**:`POST /messages/`
+- **Content-Type**:`application/json`
+
+### 8.1 请求头(应用签名)
+
+| Header | 说明 |
+|--------|------|
+| `Content-Type` | `application/json` |
+| `X-App-Id` | 应用字符串 ID,与 `APP_ID` 一致 |
+| `X-Timestamp` | Unix 秒(与签名、Body 中逻辑一致) |
+| `X-Sign` | 对 `app_id=<X-App-Id>&timestamp=<X-Timestamp>` 的 HMAC-SHA256 十六进制 |
+
+#### `content_type` 取值说明
+
+与后端枚举一致,发送时传**字符串**(大写)。未传时默认 **`TEXT`**。
+
+| 取值 | 含义 | `content` 建议形态 |
+|------|------|-------------------|
+| `TEXT` | 普通文本通知 | 字符串 |
+| `IMAGE` | 图片 | 对象存储中的 **Object Key** 或经平台处理的资源标识;若误传完整 URL,服务端可能尝试抽取 Key |
+| `VIDEO` | 视频 | 同上 |
+| `FILE` | 文件 | 同上 |
+| `USER_NOTIFICATION` | **业务/申请类通知**(会话列表中突出标题、正文可结构化) | **字符串**或 **JSON 对象**;传对象时服务端会序列化为 JSON 字符串存储,客户端常对 `content` 做 `JSON.parse` 解析 |
+
+**与 `auto_sso` 的关系**:当 `auto_sso=true` 且已提供 `target_url`、应用 `app_id` 时,只要 **`type` 为 `NOTIFICATION`** 或 **`content_type` 为 `USER_NOTIFICATION`**,平台都会按同一规则生成 **SSO jump** 形式的 `action_url`(见下文示例)。
+
+**选用建议**:常规系统通知用 `TEXT`;需要「申请单样式」、富文本结构或前端自定义渲染时,可改用 `USER_NOTIFICATION` 并在 `content` 中传结构化数据。
+
+---
+
+### 8.2 单用户系统通知(`type: NOTIFICATION`,非广播)
+
+向**一名**接收者推送。接收人二选一指定:
+
+- **`receiver_id`**:平台用户 ID(整数);或
+- **`app_id` + `app_user_id`**:应用字符串 ID + 该用户在本应用映射中的外部账号。
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `type` | string | 是 | 固定 `NOTIFICATION` |
+| `title` | string | 是 | 标题,最大 255 字 |
+| `content` | string \| object | 是 | 正文;`content_type` 为 `USER_NOTIFICATION` 时可传对象,见 **8.1 `content_type` 取值说明** |
+| `content_type` | string | 否 | 默认 `TEXT`;可选:`TEXT`、`IMAGE`、`VIDEO`、`FILE`、`USER_NOTIFICATION`(见 **8.1**) |
+| `receiver_id` | int | 条件 | 与 `app_id`+`app_user_id` 二选一 |
+| `app_id` | string | 条件 | 与 `app_user_id` 同时出现时用于解析接收人 |
+| `app_user_id` | string | 条件 | 接收方在本应用映射中的 ID |
+| `action_url` | string \| null | 否 | 自定义跳转;若 `auto_sso` 为 true 可能被平台改写 |
+| `action_text` | string \| null | 否 | 按钮文案 |
+| `target_url` | string \| null | 否 | `auto_sso=true` 时作为 SSO 目标页,用于生成 jump 链接 |
+| `auto_sso` | boolean | 否 | 默认 `false`;为 true 时可生成 `/api/v1/simple/sso/jump?...` 形式的 `action_url` |
+| `sender_app_user_id` | string \| null | 否 | 可选,标识应用侧「发起人」映射,用于审计展示 |
+
+**约束**:`is_broadcast` 为 `false` 或不传;须提供 `receiver_id` **或** (`app_id` + `app_user_id`)。
+
+**请求 / 响应示例(按 `app_user_id` 指定接收人,含 SSO 跳转)**
+
+```http
+POST https://api.hnyunzhu.com/api/v1/messages/ HTTP/1.1
+Content-Type: application/json
+X-App-Id: app_demo_001
+X-Timestamp: 1710000000
+X-Sign: 1a2b3c4d5e6f708192a3b4c5d6e7f8090a1b2c3d4e5f678901234567890abcd
+
+{
+  "app_id": "app_demo_001",
+  "app_user_id": "ext_user_1001",
+  "type": "NOTIFICATION",
+  "content_type": "TEXT",
+  "title": "审批提醒",
+  "content": "您有一条待办审批",
+  "auto_sso": true,
+  "target_url": "https://oa.example.com/approve/123",
+  "action_text": "去处理"
+}
+```
+
+```json
+{
+  "id": 90001,
+  "sender_id": null,
+  "receiver_id": 1001,
+  "app_id": 12,
+  "app_name": "演示应用",
+  "type": "NOTIFICATION",
+  "content_type": "TEXT",
+  "title": "审批提醒",
+  "content": "您有一条待办审批",
+  "action_url": "/api/v1/simple/sso/jump?app_id=app_demo_001&redirect_to=https%3A%2F%2Foa.example.com%2Fapprove%2F123",
+  "action_text": "去处理",
+  "is_read": false,
+  "created_at": "2026-04-07T10:00:00",
+  "read_at": null
+}
+```
+
+**补充示例(用平台用户 ID 指定接收人:`receiver_id`)**
+
+```http
+POST https://api.hnyunzhu.com/api/v1/messages/ HTTP/1.1
+Content-Type: application/json
+X-App-Id: app_demo_001
+X-Timestamp: 1710000001
+X-Sign: <按 X-App-Id 与 X-Timestamp 计算的签名>
+
+{
+  "type": "NOTIFICATION",
+  "content_type": "TEXT",
+  "title": "到账提醒",
+  "content": "您的订单已支付成功",
+  "receiver_id": 1001
+}
+```
+
+响应体结构与上一示例相同,字段值随请求变化(如 `receiver_id` 为 `1001`)。
+
+**补充示例(`content_type: USER_NOTIFICATION`,结构化 `content` + SSO)**
+
+```http
+POST https://api.hnyunzhu.com/api/v1/messages/ HTTP/1.1
+Content-Type: application/json
+X-App-Id: app_demo_001
+X-Timestamp: 1710000002
+X-Sign: <按 X-App-Id 与 X-Timestamp 计算的签名>
+
+{
+  "app_id": "app_demo_001",
+  "app_user_id": "ext_user_1001",
+  "type": "NOTIFICATION",
+  "content_type": "USER_NOTIFICATION",
+  "title": "请假申请待审批",
+  "content": {
+    "applicant": "张三",
+    "days": 3,
+    "reason": "年假"
+  },
+  "auto_sso": true,
+  "target_url": "https://oa.example.com/hr/leave/999",
+  "action_text": "去审批"
+}
+```
+
+**响应 `200`(节选):** `content` 存为 JSON 字符串;`content_type` 为 `USER_NOTIFICATION`;`action_url` 在 `auto_sso` 为 true 时同样可生成 jump 链接。
+
+```json
+{
+  "type": "NOTIFICATION",
+  "content_type": "USER_NOTIFICATION",
+  "title": "请假申请待审批",
+  "content": "{\"applicant\":\"张三\",\"days\":3,\"reason\":\"年假\"}",
+  "action_url": "/api/v1/simple/sso/jump?app_id=app_demo_001&redirect_to=..."
+}
+```
+
+### 8.3 广播(全员系统通知)
+
+仅应用可调;向平台内**全部活跃用户**各投递一条通知。
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| `is_broadcast` | boolean | 是 | 必须为 `true` |
+| `type` | string | 是 | 必须为 `NOTIFICATION` |
+| `title` | string | 是 | 标题 |
+| `content` | string \| object | 是 | 正文 |
+| `content_type` | string | 否 | 默认 `TEXT`;可选值见 **8.1**(`USER_NOTIFICATION` 等) |
+| `auto_sso` | boolean | 否 | 是否对跳转链接做 SSO 封装 |
+| `target_url` | string \| null | 否 | 与 `auto_sso` 配合使用 |
+| `action_url` | string \| null | 否 | 自定义跳转(不走 SSO 时) |
+| `action_text` | string \| null | 否 | 按钮文案 |
+
+**约束**:不要传 `receiver_id`、`app_user_id`;广播仅支持通知类型。
+
+**请求示例**
+
+```http
+POST https://api.hnyunzhu.com/api/v1/messages/ HTTP/1.1
+Content-Type: application/json
+X-App-Id: app_demo_001
+X-Timestamp: 1710000003
+X-Sign: 5e6f708192a3b4c5d6e7f8090a1b2c3d4e5f678901234567890abcdef0123
+
+{
+  "is_broadcast": true,
+  "type": "NOTIFICATION",
+  "content_type": "TEXT",
+  "title": "系统维护",
+  "content": "今晚 22:00-24:00 维护"
+}
+```
+
+**响应 `200`:** 返回 **`MessageResponse`**,与「为全体用户各创建一条通知」中的**第一条**记录对应(便于对接方拿到一条结构化回包);其余用户的消息结构相同,仅 `id`、`receiver_id` 等不同。若平台无活跃用户则返回 `400`,`detail` 为「没有可发送的活跃用户」。
+
+```json
+{
+  "id": 90010,
+  "sender_id": null,
+  "receiver_id": 1001,
+  "app_id": 12,
+  "app_name": "演示应用",
+  "type": "NOTIFICATION",
+  "content_type": "TEXT",
+  "title": "系统维护",
+  "content": "今晚 22:00-24:00 维护",
+  "action_url": null,
+  "action_text": null,
+  "is_read": false,
+  "created_at": "2026-04-07T10:00:00",
+  "read_at": null
+}
+```
+
+---
+
+### 8.4 成功响应 `MessageResponse`(发送接口共性)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `id` | int | 消息 ID |
+| `sender_id` | int \| null | 发送方用户 ID;应用系统通知可能为 `null` |
+| `receiver_id` | int | 接收方用户 ID |
+| `app_id` | int \| null | 应用**数据库主键**(整数) |
+| `app_name` | string \| null | 应用名称 |
+| `type` | string | 本文档场景下为 `NOTIFICATION`(平台尚存在其他类型,见 OpenAPI) |
+| `content_type` | string | 内容类型:`TEXT`、`IMAGE`、`VIDEO`、`FILE`、`USER_NOTIFICATION` |
+| `title` | string | 标题 |
+| `content` | string | 正文 |
+| `action_url` | string \| null | 跳转链接(系统通知可能存在) |
+| `action_text` | string \| null | 按钮文案 |
+| `is_read` | bool | 是否已读 |
+| `created_at` | datetime | 创建时间 ISO8601 |
+| `read_at` | datetime \| null | 已读时间 |
+
+**返回示例(`200 OK`,单条系统通知,与上文发送示例对应):**
+
+```json
+{
+  "id": 90001,
+  "sender_id": null,
+  "receiver_id": 1001,
+  "app_id": 12,
+  "app_name": "演示应用",
+  "type": "NOTIFICATION",
+  "content_type": "TEXT",
+  "title": "审批提醒",
+  "content": "您有一条待办审批",
+  "action_url": "/api/v1/simple/sso/jump?app_id=app_demo_001&redirect_to=https%3A%2F%2Foa.example.com%2Fapprove%2F123",
+  "action_text": "去处理",
+  "is_read": false,
+  "created_at": "2026-04-07T10:00:00",
+  "read_at": null
+}
+```
+
+(广播成功时结构相同,通常 `receiver_id` 为本次作为「代表返回」的那条消息对应的用户 ID;`action_url`、`app_id` 等以实际响应为准。)
+
+---
+
+## 9. 调试与规范来源
+
+- **交互式 API 文档**:`https://api.hnyunzhu.com/api/v1/docs`(Swagger)。
+- 若本文与线上部署不一致,以**实际 OpenAPI 与接口返回**为准。
+
+---
+
+## 10. 安全要点(摘要)
+
+1. `APP_SECRET` 仅留在服务端;任何含 Secret 的签名不得在浏览器完成。  
+2. 区分 `UAP_API_BASE` 与 `UAP_WEB_BASE`。  
+3. `APP_ACCESS_TOKEN` 与 JWT 同样属于敏感凭据。  
+4. SSO 与 Ticket 流程务必使用 HTTPS 与合法回调域。
+
+---
+
+*文档版本:与当前仓库后端 Schema/路由一致时,API 前缀为 `/api/v1`。*

+ 1212 - 0
docs/LLM_UAP_INTEGRATION.pdf

@@ -0,0 +1,1212 @@
+UAP 系统对接说明(面向自动化阅读)
+
+本文档独立成篇,描述如何在业务系统中对接 Unified Authentication Platform(UAP,统一认证平台)。阅读者应能仅凭本文完成:配置项填写、登
+录、SSO、用户查询与映射同步、消息发送(系统通知与广播)。
+
+示例约定
+
+下文示例中:
+
+表示 (生产环境统一认证 ); https://api.hnyunzhu.com/api/v1
+                        UAP_API_BASE                  API
+、 、 等为占位符,需替换为真实值; app_demo_001 your_app_secret eyJhbG...
+sign 为按第 4 节算法计算得到的 64 位小写十六进制字符串,示例里用 deadbeef... 形式表示,不能直接复制使用;
+时间戳 1710000000 仅为示例,调用时需使用当前 Unix 秒。
+
+1. 两类基础地址
+
+  变量名                                       含义                                                   示例
+                  HTTP(S) API 根路径,必须包含版本前缀 /api/v1
+UAP_API_BASE      用户浏览器访问的前端站点根(用于跳转登录页;本部署与 API 同主机)                                 https://api.hnyunzhu.com/api/v1
+UAP_WEB_BASE                                                                          https://api.hnyunzhu.com
+
+下文所有接口路径均相对于 拼接,例如: 。 UAP_API_BASE
+                        UAP_API_BASE + /simple/login → https://api.hnyunzhu.com/api/v1/simple/login
+
+跳转登录页使用 : UAP_WEB_BASE
+
+: PC {UAP_WEB_BASE}/login?app_id=<APP_ID>
+移动端 : H5 {UAP_WEB_BASE}/mobile/login?app_id=<APP_ID>
+
+2. 对接前必填配置
+
+在 UAP 管理后台创建应用后,请收集并安全保存以下信息:
+
+     配置项                                                   说明
+
+APP_ID            应用对外标识(字符串,如 ) app_xxxxxxxx
+APP_SECRET
+APP_ACCESS_TOKEN  用于 HMAC-SHA256 签名,仅允许在业务服务端使用,禁止写入前端或客户端
+REDIRECT_URIS
+                  应用访问令牌,用于 接口( : ) M2M
+                                                      HTTP Header X-App-Access-Token
+
+                  在应用中配置的回调地址列表; 、 、 Redirect SSO sso-login exchange 等流程依赖合法回调
+
+可选:若使用标准 OIDC 协议,还需 OIDC 客户端凭据(本文不展开 OIDC 全流程)。
+
+2.1 对接方填写(凭据留空)
+
+实施对接时,请将贵司从 UAP 管理后台获取的值填入下表(可打印或复制后替换正文中的占位符)。App Secret 仅保存在服务端,勿提交到代码仓库或
+前端。 下表中 API / 前端根地址 已按当前生产环境填写;若贵司使用独立域名部署,请改为实际地址。
+
+                项目                       变量名(本文档)                              填写
+API 根地址
+前端站点根地址                               UAP_API_BASE             https://api.hnyunzhu.com/api/v1
+                                      UAP_WEB_BASE             https://api.hnyunzhu.com
+                项目                              变量名(本文档)                                  填写
+
+App ID                                 APP_ID
+
+App Secret                             APP_SECRET
+                                       APP_ACCESS_TOKEN
+Access Token(应用访问令牌)
+
+说明:
+
+、 :用于 签名、消息接口 等。 App ID App Secret
+                    Simple Auth                 X-App-Id / X-Sign
+:用于 ,对应 (用户搜索、映射同步等);不是 。 Access Token
+                HTTP Header X-App-Access-Token  M2M                  App Secret
+UAP_API_BASE 必须包含版本前缀 /api/v1 ; UAP_WEB_BASE 为浏览器访问登录页所用站点根(本部署与 API 同主机时为
+)。 https://api.hnyunzhu.com
+
+3. 认证方式总览
+
+对接时会遇到四类身份校验,请勿混用:
+
+        方式                       典型场景                                请求形式
+
+A. JSON Body    、 、 /simple/login /simple/validate /simple/exchange  内含 、 ,用 对除 Body                  sign
+                等                                                    timestamp sign       APP_SECRET
+签名(Simple                                                            外的参数做 (见第 HMAC-SHA256 4 节)
+) Auth
+
+B. HTTP Header  服务端代应用调用 POST /messages/                             : 、 、 (签名字符串仅含 Header X-App-Id X-Timestamp X-Sign
+                用户已登录 UAP 后调用需身份的接口(如                                与 ,见第 节) app_id timestamp
+签名              、管理类接口等) POST /auth/login/json                                       4.2
+(应用调消息接口)
+
+用户 C.   JWT                                                           Authorization: Bearer <access_token>
+
+D. M2M 访问令牌 用户搜索、映射同步、全量用户拉取                                         (部分场景亦支持应用 X-App-Access-Token: <APP_ACCESS_TOKEN>
+                                                                     JWT Bearer,与平台实现一致)
+
+签名算法( ) 4.
+                HMAC-SHA256
+
+平台校验逻辑与实现一致:参数键名按 ASCII 升序排列,拼接为 (不含 key1=value1&key2=value2 sign ),再对拼接串做 ,密钥为 HMAC-SHA256
+UTF-8 编码的 APP_SECRET ,结果为小写 十六进制字符串。
+时间戳: timestamp 为 Unix 秒级整数;服务端对时间偏差有约 300 秒容差。
+
+( ) 4.1 Simple Auth JSON Body
+
+参与签名的键为请求 JSON 中除 sign 以外的所有键( None 值一般应排除,与常见实现一致);仅包含实际发送的字段。各接口以下文「参与签名的字
+段」为准。
+示例: Python
+  import hmac
+  import hashlib
+
+  def sign_simple_auth(secret: str, params: dict) -> str:
+         data = {k: v for k, v in params.items() if k != "sign" and v is not None}
+         query_string = "&".join(f"{k}={data[k]}" for k in sorted(data.keys()))
+         return hmac.new(
+                secret.encode("utf-8"),
+                query_string.encode("utf-8"),
+                hashlib.sha256,
+         ).hexdigest()
+
+4.2 消息接口(Header 签名)
+
+仅对两个键签名: app_id (即 X-App-Id 的值)、 (即 timestamp X-Timestamp 的字符串值,与 Header 中一致)。拼接串为:
+
+ app_id=<APP_ID>&timestamp=<TIMESTAMP>
+
+对该字符串做 ,结果放入 HMAC-SHA256 X-Sign 。
+消息 Header 签名示例(伪代码):
+
+   待签名字符串: app_id=app_demo_001&timestamp=1710000000
+   X-Timestamp 取值与字符串中的 timestamp 完全一致(通常为数字的十进制字符串,如 )。 "1710000000"
+
+  X-App-Id: app_demo_001
+  X-Timestamp: 1710000000
+  X-Sign: 1a2b3c4d5e6f708192a3b4c5d6e7f8090a1b2c3d4e5f678901234567890abcd
+
+5. 登录相关接口
+
+(密码登录) 5.1 POST /simple/login
+
+   : Content-Type application/json
+   说明:未提供 app_id 时为平台账号登录(返回用户 JWT);提供 app_id 时为应用 SSO 登录(返回 ticket )。应用侧若提供 sign /
+   timestamp ,二者须同时出现,且服务端会校验签名(推荐服务端调用时始终带签名)。
+
+请求体字段
+
+   字段           类型          必填                                        说明
+
+app_id       string | null  否 不提供:平台登录;提供:应用 SSO,返回 ticket
+identifier   string
+password     string         是 用户标识:手机号、映射 mapped_key 或映射邮箱等(平台登录时按手机号查找)
+remember_me  boolean
+timestamp    int            是 登录密码
+sign         string
+                            否 默认 false ;为 true 时 JWT 有效期延长(仅平台登录生效)
+
+                            条件 与 sign 同时提供或同时省略;提供时参与签名
+
+                            条件 HMAC-SHA256 十六进制;应用 SSO 时推荐必带
+
+应用 且启用签名时,参与签名的字段为: 、 、 、 (不含 )。 SSO
+                                app_id identifier password timestamp      remember_me
+
+响应 : 200 PasswordLoginResponse
+
+    字段          类型                       何时出现                       说明
+                                提供 app_id 且登录成功  临时票据,供 使用 POST /simple/validate
+ticket       string | null
+    字段                  类型                   何时出现                                                说明
+                                    未提供 app_id 且登录成功                          用户 JWT
+access_token         string | null  平台登录成功                                    固定为 bearer
+token_type           string | null  平台登录成功                                    如 、 、 SUPER_ADMIN DEVELOPER ORDINARY_USER
+role                 string | null
+
+应用 成功时典型返回: (无 )。 SSO
+                     { "ticket": "<票据字符串>" }                  access_token
+
+调用与返回示例
+
+A. 应用 SSO(带签名,返回 ticket )
+
+POST https://api.hnyunzhu.com/api/v1/simple/login HTTP/1.1
+Content-Type: application/json
+
+{
+   "app_id": "app_demo_001",
+   "identifier": "13800138000",
+   "password": "UserPassword123",
+   "timestamp": 1710000000,
+   "sign": "1a2b3c4d5e6f708192a3b4c5d6e7f8090a1b2c3d4e5f678901234567890abcd"
+
+}
+
+由 对以下键签名(不含 ): 、 、 、 。 sign APP_SECRET
+                                    sign app_id identifier password timestamp
+
+响应 200 :
+
+{
+   "ticket": "TICKET-7f8e9d0a-1234-5678-abcd-ef0123456789"
+
+}
+
+B. 平台登录(无     ,返回 ) app_id
+                     JWT
+
+  POST https://api.hnyunzhu.com/api/v1/simple/login HTTP/1.1
+  Content-Type: application/json
+
+  {
+      "identifier": "13800138000",
+      "password": "UserPassword123",
+      "remember_me": false
+
+  }
+
+响应 200 :
+
+  {
+      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.xxx",
+      "token_type": "bearer",
+      "role": "ORDINARY_USER"
+
+  }
+
+错误示例(密码错误, 401 ):
+
+{
+   "detail": "密码错误"
+
+}
+常见错误 HTTP 状态与 detail (节选)
+
+        状态码                                                                     含义(示例)
+
+400                         用户已禁用、签名无效(应用侧)
+
+401                         密码错误
+
+404                         用户未找到、应用未找到
+
+(短信验证码登录) 5.2 POST /simple/sms-login
+
+   : Content-Type application/json
+   说明:逻辑与 5.1 对称——无 app_id 返回平台 JWT;有 app_id 返回 ticket 。签名可选;若带 sign 必须同时带 timestamp 。短信功能需在平
+   台开启(否则可能 403 )。
+
+请求体字段
+
+   字段           类型          必填                                                  说明
+
+mobile       string         是 手机号
+code         string
+remember_me  boolean        是 短信验证码
+app_id       string | null
+timestamp    int            否 默认 false ;影响平台登录 JWT 有效期
+sign         string
+                            否 不提供:平台登录;提供:应用 SSO,返回 ticket
+
+                            条件 与 sign 成对出现
+
+                            条件 应用 SSO 且启用签名时:参与签名的字段为 、 、 、 app_id mobile code timestamp
+
+响应 200
+
+与 相同,使用 ( 或 )。 5.1
+             PasswordLoginResponse ticket  access_token + token_type + role
+
+调用与返回示例
+
+应用 SSO(带签名)
+
+  POST https://api.hnyunzhu.com/api/v1/simple/sms-login HTTP/1.1
+  Content-Type: application/json
+
+  {
+      "app_id": "app_demo_001",
+      "mobile": "13800138000",
+      "code": "123456",
+      "timestamp": 1710000000,
+      "sign": "2b3c4d5e6f708192a3b4c5d6e7f8090a1b2c3d4e5f678901234567890abcde"
+
+  }
+
+参与签名的键: 、 、 、 。 app_id mobile code timestamp
+响应 200 :
+
+  {
+      "ticket": "TICKET-8a9b0c1d-2345-6789-bcde-f01234567890"
+
+  }
+
+平台短信登录(无 app_id )
+  {
+      "mobile": "13800138000",
+      "code": "123456",
+      "remember_me": false
+
+  }
+
+响应 200 :
+
+{
+   "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+   "token_type": "bearer",
+   "role": "ORDINARY_USER"
+
+}
+
+常见错误(节选)
+
+       状态码                                                                     含义(示例)
+
+400                             验证码错误或已过期、用户已禁用
+403                             短信登录功能未开启
+404                             用户未找到、应用未找到
+
+(验证票据) 5.3 POST /simple/validate
+
+: Content-Type application/json                对 签名( Body
+认证:必须使用 目标应用的                                                                  必填)。
+                                APP_SECRET                 sign  /  timestamp
+
+请求体字段
+
+     字段                 类型        必填                                              说明
+                                 是                   来自登录跳转、 exchange 、 sso-login 等流程的票据
+ticket                string     是                   消费票据的应用 ID(与为该应用签名的 Secret 一致)
+app_id                string     是                   秒 Unix
+timestamp             int        是                   参与签名的字段为: 、 、 ticket app_id timestamp
+sign                  string
+
+响应 : 200 TicketValidateResponse
+
+票据有效时:
+
+       字段                        类型                                                    说明
+
+    valid                       bool                  true
+    user_id                     int
+    mobile                      string               平台用户主键
+    mapped_key                  string | null        用户手机号
+    mapped_email                string | null        该用户在当前应用下的映射账号(无映射时为 null )
+                                                     该用户在当前应用下的映射邮箱
+票据无效或已消费:
+
+                  字段                           类型                                      说明
+
+valid                                          bool            false
+            字段                                  类型                            说明
+、 等 user_id mobile                                  一般为 null 或省略(以实际 JSON 为准)
+                                              —
+
+调用与返回示例
+
+参与签名的键: 、 、 (不含 )。 ticket app_id timestamp
+                                              sign
+
+  POST https://api.hnyunzhu.com/api/v1/simple/validate HTTP/1.1
+  Content-Type: application/json
+
+  {
+      "app_id": "app_demo_001",
+      "ticket": "TICKET-7f8e9d0a-1234-5678-abcd-ef0123456789",
+      "timestamp": 1710000001,
+      "sign": "3c4d5e6f708192a3b4c5d6e7f8090a1b2c3d4e5f678901234567890abcdef"
+
+  }
+
+响应 200 (成功):
+
+  {
+      "valid": true,
+      "user_id": 1001,
+      "mobile": "13800138000",
+      "mapped_key": "ext_user_001",
+      "mapped_email": "zhangsan@example.com"
+
+  }
+
+响应 200 (票据无效或已使用):
+
+  {
+      "valid": false
+
+  }
+
+错误示例(签名错误, 400 ):
+
+  {                                                                            含义(示例)
+      "detail": "签名无效"
+                                                    签名无效
+  }                                                 应用未找到
+
+常见错误
+
+                状态码
+
+  400
+
+  404
+
+5.4 平台用户登录(获取 JWT,非 Simple 票据流程)
+
+ POST /auth/login/json
+
+   : Content-Type application/json
+请求体:
+      字段                    类型           必填                                      说明
+
+mobile                 string         是                          用户手机号(平台账号)
+password               string
+remember_me            boolean        是                          密码
+app_id                 string | null
+                                      否                          默认 false ; true 时使用更长有效期 JWT
+
+                                      否                          可选;直接平台登录通常可省略
+
+响应 200 :
+
+                       字段                       类型                                    说明
+                                                                     用户 JWT
+access_token                             string                      固定为 bearer
+token_type                               string
+
+常见错误: 400 (手机号或密码错误、账户已禁用)、 403 (账户待审核)。
+
+调用与返回示例
+
+POST /auth/login/json
+
+  POST https://api.hnyunzhu.com/api/v1/auth/login/json HTTP/1.1
+  Content-Type: application/json
+
+  {
+      "mobile": "13800138000",
+      "password": "UserPassword123",
+      "remember_me": true
+
+  }
+
+响应 200 :
+
+ {
+     "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+     "token_type": "bearer"
+
+ }
+
+(表单) POST /auth/login
+
+  POST https://api.hnyunzhu.com/api/v1/auth/login HTTP/1.1
+  Content-Type: application/x-www-form-urlencoded
+
+  username=13800138000&password=UserPassword123
+
+响应 200 : 与 JSON 登录相同。
+错误示例( 400 ):
+
+  {
+      "detail": "手机号或密码错误"
+
+  }
+
+( POST /auth/login 表单字段与响应与上表一致,见上文 HTTP 示例。)
+6. SSO 相关接口
+
+6.1 浏览器跳转登录(Redirect ) SSO
+
+非 JSON 接口,步骤如下:
+
+浏览器访问: 或 。 1.
+              {UAP_WEB_BASE}/login?app_id=<APP_ID> {UAP_WEB_BASE}/mobile/login?app_id=<APP_ID>
+2. 用户在 UAP 登录成功后,浏览器重定向到应用配置的回调 URL,查询参数携带 ticket=<票据> 。
+3. 应用服务端调用 POST /simple/validate (见 5.3)换用户信息并建立会话。
+
+前提:回调地址必须在应用 REDIRECT_URIS 中配置。
+
+调用与返回示例
+
+浏览器打开登录页(仅说明 URL,无 JSON ): Body
+
+  https://api.hnyunzhu.com/login?app_id=app_demo_001
+
+登录成功后,浏览器跳转到应用回调(示例):
+
+  https://biz.example.com/oauth/callback?ticket=TICKET-7f8e9d0a-1234-5678-abcd-ef0123456789
+
+业务后端再使用 的 换用户信息。 5.3 POST /simple/validate
+
+( 获取跳转 ) 6.2 POST /simple/sso-login API
+                                                      URL
+
+: Content-Type application/json
+说明:返回业务系统带 ticket 的完整 redirect_url ,客户端应导航至该 URL。目标应用须存在;SIMPLE_API 应用返回 ; 回调地址?ticket=...
+OIDC 应用返回简化后的根 URL(仅 scheme + netloc ),详见下表。
+
+请求头(二选一)
+
+    模式                                                      请求头 / Body
+会话模式
+凭据模式          ; 仅需目标应用 Authorization: Bearer <用户 access_token> Body  app_id
+
+              无 ; 须含 、 、 Bearer Body
+                                  app_id username password
+
+请求体字段
+
+    字段          类型            必填                                                             说明
+
+app_id        string       是      要进入的目标应用 ID
+username      string
+password      string       条件 未带有效 Bearer 时必填:手机号、映射 key 或映射邮箱等
+
+                           条件     未带有效 Bearer 时必填
+
+响应 : 200 SsoLoginResponse
+
+  字段          类型                                            说明
+
+redirect_url  string  : ; :回调 解析后的 (无路径) SIMPLE_API {首个 redirect_uri}?ticket=<票据> OIDC
+                                                                                             URI  scheme://host[:port]
+
+常见错误
+
+   状态码                                             含义(示例)
+              应用未找到、未配置重定向 URI、用户已禁用、协议不支持等
+400
+   状态码                                                            含义(示例)
+
+401            认证失败
+
+调用与返回示例
+
+会话模式(已持有用户 ) JWT
+
+  POST https://api.hnyunzhu.com/api/v1/simple/sso-login HTTP/1.1
+  Content-Type: application/json
+  Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
+
+  {
+      "app_id": "target_app_002"
+
+  }
+
+响应 ( 应用): 200 SIMPLE_API
+
+  {
+      "redirect_url": "https://biz.example.com/callback?ticket=TICKET-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
+
+  }
+
+凭据模式(未登录)
+
+POST https://api.hnyunzhu.com/api/v1/simple/sso-login HTTP/1.1
+Content-Type: application/json
+
+{
+   "app_id": "target_app_002",
+   "username": "13800138000",
+   "password": "UserPassword123"
+
+}
+
+响应 : 同上,为带 的 。 200
+               ticket redirect_url
+
+(源应用用户免登进目标应用) 6.3 POST /simple/exchange
+
+   : Content-Type application/json
+   签名:使用源应用 ; 必填。 APP_SECRET sign / timestamp
+
+请求体字段
+
+     字段         类型                 必填                                说明
+                                  是    源应用 ID
+app_id         string             是    目标应用 ID
+target_app_id  string             是    用户在 UAP 的手机号(须已存在)
+user_mobile    string             是    秒 Unix
+timestamp      int                是    参与签名: 、 、 、 app_id target_app_id user_mobile timestamp
+sign           string
+
+响应 : 200 TicketExchangeResponse
+
+字段 类型                                                             说明
+
+ticket         string 目标应用可用的票据
+  字段          类型                                                               说明
+
+redirect_url  string  目标应用首个 redirect_uri 拼接 ?ticket=<ticket> 的完整 URL(若未配置合法 URI,实现中可能回退占位,
+                      生产环境应保证配置正确)
+
+常见错误
+
+        状态码                                                                    含义(示例)
+
+400                           签名无效
+404                           源应用 / 目标应用 / 用户未找到
+
+目标应用收到 后,用目标应用的 调用 。 ticket
+                              app_id + APP_SECRET  POST /simple/validate
+
+调用与返回示例
+
+参与签名的键: 、 、 、 (使用源应用 )。 app_id target_app_id user_mobile timestamp
+                                                                               Secret
+
+  POST https://api.hnyunzhu.com/api/v1/simple/exchange HTTP/1.1
+  Content-Type: application/json
+
+  {
+      "app_id": "source_app_001",
+      "target_app_id": "target_app_002",
+      "user_mobile": "13800138000",
+      "timestamp": 1710000002,
+      "sign": "4d5e6f708192a3b4c5d6e7f8090a1b2c3d4e5f678901234567890abcdef01"
+
+  }
+
+响应 200 :
+
+{
+   "ticket": "TICKET-bbbbcccc-dddd-eeee-ffff-000011112222",
+   "redirect_url": "https://target.example.com/callback?ticket=TICKET-bbbbcccc-dddd-eeee-ffff-000011112222"
+
+}
+
+(通知内嵌 跳转) 6.4 GET /simple/sso/jump
+                                         SSO
+
+用于消息中配置的跳转:用户点击后,若已在 UAP 登录则带上 Ticket 重定向到应用回调。
+
+查询参数
+
+     参数                 类型       必填                                                        说明
+
+app_id                string  是          目标应用 ID
+redirect_to           string
+                              是          登录成功后最终要到达的业务页 URL(经 URL 编码传入)
+
+响应行为(非 ) JSON
+
+    场景            HTTP                                                                 说明
+当前未登录 UAP
+当前已登录         等 307 / 302     到相对路径 (依赖网关 前端将用户导向登录页) RedirectResponse                                       /
+应用不存在         等 307 / 302                          /login?redirect=<当前完整 jump URL>
+未配置回调
+              404             重定向到应用 中首个 ,查询参数包含 、 ( 为 ) redirect_uris
+                                                   URI                                     ticket next next redirect_to
+              400
+                              说明 detail
+
+                              说明 detail
+调用与返回示例
+
+请求 ( 需 编码): URL redirect_to URL
+
+  GET https://api.hnyunzhu.com/api/v1/simple/sso/jump?app_id=app_demo_001&redirect_to=https%3A%2F%2Foa.example.com%2Fapprove%2F123
+
+已登录时: HTTP 重定向( 302 / 307 ), Location 类似:
+
+  https://biz.example.com/callback?ticket=TICKET-xxx&next=https%3A%2F%2Foa.example.com%2Fapprove%2F123
+
+未登录时: 重定向到登录页, Location 可能类似:
+
+  /login?redirect=https%3A%2F%2Fapi.hnyunzhu.com%2Fapi%2Fv1%2Fsimple%2Fsso%2Fjump%3Fapp_id%3D...
+
+7. 用户接口(M2M)
+
+以下接口使用 (或实现所支持的应用 ),不使用 的 签名。 X-App-Access-Token: <APP_ACCESS_TOKEN>
+                                                  JWT                             Simple Auth Body
+
+7.1 推荐业务流程(查询 → 建用户 / 推映射)
+
+对接方在「把业务账号与平台用户绑定」时,建议按下面顺序操作:
+
+1. 先查询用户
+   调用 GET /users/search (见 7.2),用手机号、姓名等关键词确认平台是否已存在该用户,并记录返回中的 id (平台用户 ID)、 mobile 、 name
+   等。
+       能查到:说明平台已有账号,只需为本应用维护映射( mapped_key 、 mapped_email 等)。
+       查不到:需先在平台创建用户,再推映射。创建方式包括:
+       在 UAP 管理后台人工新增;或
+       若贵司具备超级管理员能力,调用管理端 POST /users/ 创建(需管理员权限);或
+       直接使用 POST /apps/mapping/sync 的 UPSERT :当手机号在平台不存在时,可在同一请求中携带 name 等,由平台新建用户并建立映射
+       (见 7.3)。
+2. 填充本应用账号信息并同步
+   使用查询到的 (及必要时 )与业务侧 ,调用 : mobile
+                             name       mapped_key / mapped_email  POST /apps/mapping/sync
+       新增绑定: sync_action 为 UPSERT (默认),平台会插入或更新当前应用下的映射;若本次同时新建了平台用户,响应中 new_user_created
+       为 。 true
+       仅改映射:对已存在用户再次 ,可更新 UPSERT 、 、 mapped_key mapped_email is_active 等;不可通过该接口修改已存在用户的姓名、手机号
+       等基础档案(与接口校验一致)。
+       解除映射: sync_action 为 DELETE (仅删映射,不删平台用户)。
+3. 全量对账(可选)
+   需要批量拉取平台用户基础信息时,使用                   (见 )。 /apps/mapping/users
+                                   GET            7.4
+
+(用户查询) 7.2 GET /users/search
+
+   说明:按关键词搜索已激活、未删除的平台用户,用于在推映射前确认人选。支持 应用访问令牌 或用户 。 JWT
+   路径: GET /users/search
+   认证: ,或 X-App-Access-Token: <APP_ACCESS_TOKEN>
+                                                  Authorization: Bearer <用户 JWT>
+
+查询参数
+
+参数 类型 必填 默认值                                                                      说明
+
+q      否 string           —  关键词;对手机号、姓名、英文名模糊匹配( ilike )。不传时返回一批活跃用户(受 limit 限制)
+
+limit  int       否 20        最大返回条数
+说明:若使用用户 JWT 调用,结果中会排除当前登录用户本人;使用 应用令牌 时无此排除。
+
+响应 200
+
+返回 JSON 数组,元素为平台用户对象,主要字段如下:
+
+       字段                         类型                                    说明
+                                            平台用户 ID(可用于发消息时的 receiver_id 等)
+id                           int            手机号
+mobile                       string         姓名
+name                         string | null  英文名
+english_name                 string | null  如 ACTIVE
+status                       string         角色
+role                         string | null  创建时间 ISO8601
+created_at                   string         更新时间 ISO8601
+updated_at                   string         是否删除标记
+is_deleted                   int
+
+调用与返回示例
+
+  GET https://api.hnyunzhu.com/api/v1/users/search?q=13800138000&limit=20 HTTP/1.1
+  X-App-Access-Token: pA9s8d7f6g5h4j3k2l1m0n9o8p7q6r5s4t3u2v1w0
+
+响应 200 示例:
+
+[
+   {
+      "id": 1001,
+      "mobile": "13800138000",
+      "name": "张三",
+      "english_name": "zhangsan",
+      "status": "ACTIVE",
+      "role": "ORDINARY_USER",
+      "created_at": "2025-01-10T08:00:00",
+      "updated_at": "2025-06-01T12:00:00",
+      "is_deleted": 0
+   }
+
+]
+
+7.3 POST /apps/mapping/sync
+
+   : Content-Type application/json
+
+请求体字段 UserSyncRequest
+
+  字段            类型           必填                                                     说明
+
+mobile        string         是 中国大陆手机号,正则 ^1[3-9]\d{9}$ (与平台校验一致)
+name          string | null
+english_name  string | null  条件     UPSERT 时必填(非空姓名);已存在用户时不可通过此接口改姓名等基础信息;DELETE
+                                    可不依赖姓名逻辑(以 sync_action 分支为准)
+
+                             否 已废弃于 M2M;英文名由平台按规则生成
+字段                      类型 必填                                        说明
+
+password          否 string | null 按平台实现(若有)
+
+status            否 string | null 按平台实现
+
+mapped_key        string         是 外部系统用户 ID,长度 ~1 100
+
+mapped_email      string | null  否  外部邮箱
+
+                  boolean |      否  映射是否启用; null 表示不修改
+
+is_active         null
+sync_action
+                  否 string | null   (默认)或 UPSERT   DELETE
+
+响应 : 200 MappingResponse
+
+              字段                         类型                                                说明
+                                                                   映射记录 ID
+id                                  int                            应用数据库主键(整数)
+app_id                              int                            平台用户 ID
+                                    int                            映射账号
+user_id                             string | null                  映射邮箱
+mapped_key                          string | null                  用户手机号
+                                    string                         用户统一状态描述
+mapped_email                        string                         映射是否有效;DELETE 后可能为 false
+                                    bool                           是否本次新建了平台用户
+user_mobile                         bool                           若平台为新用户生成了初始密码,可能返回
+                                    string | null
+user_status
+
+is_active
+new_user_created
+
+    generated_password
+
+常见错误
+
+   状态码                                                       含义(节选)
+                        参数非法、与业务规则冲突(如重复映射、禁止修改字段)
+400                     无效 Token
+403
+
+调用与返回示例
+
+UPSERT
+
+  POST https://api.hnyunzhu.com/api/v1/apps/mapping/sync HTTP/1.1
+  Content-Type: application/json
+  X-App-Access-Token: pA9s8d7f6g5h4j3k2l1m0n9o8p7q6r5s4t3u2v1w0
+
+  {
+      "mobile": "13800138000",
+      "name": "张三",
+      "mapped_key": "ext_user_1001",
+      "mapped_email": "zhangsan@partner.com",
+      "is_active": true,
+      "sync_action": "UPSERT"
+
+  }
+
+响应 200 :
+  {
+      "id": 501,
+      "app_id": 12,
+      "user_id": 1001,
+      "mapped_key": "ext_user_1001",
+      "mapped_email": "zhangsan@partner.com",
+      "user_mobile": "13800138000",
+      "user_status": "ACTIVE",
+      "is_active": true,
+      "new_user_created": true,
+      "generated_password": null
+
+  }
+
+DELETE(仅删除映射)
+
+POST https://api.hnyunzhu.com/api/v1/apps/mapping/sync HTTP/1.1
+Content-Type: application/json
+X-App-Access-Token: pA9s8d7f6g5h4j3k2l1m0n9o8p7q6r5s4t3u2v1w0
+
+{
+   "mobile": "13800138000",
+   "mapped_key": "ext_user_1001",
+   "sync_action": "DELETE"
+
+}
+
+响应 : 结构同 , 可能为 (见字段表)。 200
+                  MappingResponse is_active        false
+
+7.4 GET /apps/mapping/users
+
+   认证: X-App-Access-Token
+
+查询参数
+
+       参数                               类型                必填                    默认值                 说明
+                                                                                          跳过条数
+skip                    int                        否                     0                每页条数
+limit
+                        int                        否                     100         说明
+
+响应 : 200 UserSyncList                                                                               说明
+                                                                                          手机号
+              字段                               类型                                         姓名
+                                                                                          英文名
+total                              int                           符合条件的用户总数
+                                                                 用户列表
+items                              array
+                                                                            类型
+元素 : items[]
+        UserSyncSimple
+
+                  字段
+
+mobile                                                    string
+
+name                                                      string | null
+
+english_name                                              string | null
+调用与返回示例
+
+  GET https://api.hnyunzhu.com/api/v1/apps/mapping/users?skip=0&limit=100 HTTP/1.1
+  X-App-Access-Token: pA9s8d7f6g5h4j3k2l1m0n9o8p7q6r5s4t3u2v1w0
+
+响应 200 :
+
+  {
+      "total": 1250,
+      "items": [
+         {
+            "mobile": "13800138000",
+            "name": "张三",
+            "english_name": "zhangsan"
+         },
+         {
+            "mobile": "13900139000",
+            "name": "李四",
+            "english_name": "lisi"
+         }
+      ]
+
+  }
+
+消息发送接口(仅 ) 8.
+              POST /messages/
+
+本节仅说明由应用服务端调用、使用 Header 签名(第 3 节方式 B、第 4.2 节)发送 NOTIFICATION 的两种形态:
+ 1. 单用户系统通知(指定一名接收者)
+ 2. 广播(向全体活跃用户各发一条)
+不涉及用户间私信( type: MESSAGE )及用户 JWT 代发,对接方无需关注。
+
+   路径: POST /messages/
+   : Content-Type application/json
+
+8.1 请求头(应用签名)
+
+Header                                                                              说明
+
+Content-Type   application/json
+X-App-Id
+X-Timestamp   应用字符串 ID,与 APP_ID 一致
+X-Sign        Unix 秒(与签名、Body 中逻辑一致)
+              对 的 十六进制 app_id=<X-App-Id>&timestamp=<X-Timestamp> HMAC-SHA256
+
+取值说明 content_type
+
+与后端枚举一致,发送时传字符串(大写)。未传时默认 TEXT 。
+
+    取值                     含义                        建议形态 content
+              普通文本通知              字符串
+TEXT          图片                  对象存储中的 Object Key 或经平台处理的资源标识;若误传完整 , URL
+IMAGE                             服务端可能尝试抽取 Key
+VIDEO         视频                  同上
+    取值                           含义                                     建议形态 content
+                    文件                               同上
+FILE                业务/申请类通知(会话列表中突出标题、              字符串或 JSON 对象;传对象时服务端会序列化为 JSON 字符串存储,
+                    正文可结构化)                          客户端常对 做 解析 content JSON.parse
+USER_NOTIFICATION
+
+与 的关系:当 且已提供 、应用 时,只要 为 或 为 auto_sso
+                    auto_sso=true        target_url  app_id         type NOTIFICATION content_type
+,平台都会按同一规则生成 形式的 (见下文示例)。 USER_NOTIFICATION
+                                     SSO jump        action_url
+
+选用建议:常规系统通知用 TEXT ;需要「申请单样式」、富文本结构或前端自定义渲染时,可改用 USER_NOTIFICATION 并在 content 中传结构化数
+据。
+
+单用户系统通知( ,非广播) 8.2
+                    type: NOTIFICATION
+
+向一名接收者推送。接收人二选一指定:
+
+receiver_id :平台用户 ID(整数);或
+app_id + app_user_id :应用字符串 ID + 该用户在本应用映射中的外部账号。
+
+     字段                类型            必填                                       说明
+
+type                string           是   固定 NOTIFICATION
+title               string
+content             string | object  是 标题,最大 255 字
+content_type        string
+receiver_id         int              是   正文; 为 时可传对象,见 取值说明 content_type USER_NOTIFICATION
+app_id              string                                                                    8.1 content_type
+app_user_id         string
+action_url          string | null    否   默认 ;可选: 、 、 、 、 (见 ) TEXT
+action_text         string | null                         TEXT IMAGE VIDEO FILE USER_NOTIFICATION   8.1
+target_url          string | null
+auto_sso            boolean          条件 与 二选一 app_id + app_user_id
+sender_app_user_id  string | null
+                                     条件 与 app_user_id 同时出现时用于解析接收人
+
+                                     条件 接收方在本应用映射中的 ID
+
+                                     否 自定义跳转;若 auto_sso 为 true 可能被平台改写
+
+                                     否 按钮文案
+
+                                     否   auto_sso=true 时作为 SSO 目标页,用于生成 链接 jump
+
+                                     否   默认 ;为 时可生成 形式的 false
+                                                     true        /api/v1/simple/sso/jump?...  action_url
+
+                                     否 可选,标识应用侧「发起人」映射,用于审计展示
+
+约束: 为 或不传;须提供 或 。 is_broadcast false
+                                        receiver_id ( app_id + app_user_id )
+
+请求 / 响应示例(按 app_user_id 指定接收人,含 SSO 跳转)
+POST https://api.hnyunzhu.com/api/v1/messages/ HTTP/1.1
+Content-Type: application/json
+X-App-Id: app_demo_001
+X-Timestamp: 1710000000
+X-Sign: 1a2b3c4d5e6f708192a3b4c5d6e7f8090a1b2c3d4e5f678901234567890abcd
+
+{
+   "app_id": "app_demo_001",
+   "app_user_id": "ext_user_1001",
+   "type": "NOTIFICATION",
+   "content_type": "TEXT",
+   "title": "审批提醒",
+   "content": "您有一条待办审批",
+   "auto_sso": true,
+   "target_url": "https://oa.example.com/approve/123",
+   "action_text": "去处理"
+
+}
+
+  {
+      "id": 90001,
+      "sender_id": null,
+      "receiver_id": 1001,
+      "app_id": 12,
+      "app_name": "演示应用",
+      "type": "NOTIFICATION",
+      "content_type": "TEXT",
+      "title": "审批提醒",
+      "content": "您有一条待办审批",
+      "action_url": "/api/v1/simple/sso/jump?app_id=app_demo_001&redirect_to=https%3A%2F%2Foa.example.com%2Fapprove%2F123",
+      "action_text": "去处理",
+      "is_read": false,
+      "created_at": "2026-04-07T10:00:00",
+      "read_at": null
+
+  }
+
+补充示例(用平台用户 ID 指定接收人: receiver_id )
+
+POST https://api.hnyunzhu.com/api/v1/messages/ HTTP/1.1
+Content-Type: application/json
+X-App-Id: app_demo_001
+X-Timestamp: 1710000001
+X-Sign: <按 X-App-Id 与 X-Timestamp 计算的签名>
+
+{
+   "type": "NOTIFICATION",
+   "content_type": "TEXT",
+   "title": "到账提醒",
+   "content": "您的订单已支付成功",
+   "receiver_id": 1001
+
+}
+
+响应体结构与上一示例相同,字段值随请求变化(如 receiver_id 为 1001 )。
+
+补充示例( ,结构化 ) content_type: USER_NOTIFICATION
+                            content + SSO
+POST https://api.hnyunzhu.com/api/v1/messages/ HTTP/1.1
+Content-Type: application/json
+X-App-Id: app_demo_001
+X-Timestamp: 1710000002
+X-Sign: <按 X-App-Id 与 X-Timestamp 计算的签名>
+
+{
+   "app_id": "app_demo_001",
+   "app_user_id": "ext_user_1001",
+   "type": "NOTIFICATION",
+   "content_type": "USER_NOTIFICATION",
+   "title": "请假申请待审批",
+   "content": {
+      "applicant": "张三",
+      "days": 3,
+      "reason": "年假"
+   },
+   "auto_sso": true,
+   "target_url": "https://oa.example.com/hr/leave/999",
+   "action_text": "去审批"
+
+}
+
+响应 (节选): 存为 字符串; 为 ; 在 为 时同样可生成 链接。 200
+              content  JSON             content_type     USER_NOTIFICATION action_url  auto_sso true   jump
+
+{
+   "type": "NOTIFICATION",
+   "content_type": "USER_NOTIFICATION",
+   "title": "请假申请待审批",
+   "content": "{\"applicant\":\"张三\",\"days\":3,\"reason\":\"年假\"}",
+   "action_url": "/api/v1/simple/sso/jump?app_id=app_demo_001&redirect_to=..."
+
+}
+
+8.3 广播(全员系统通知)
+
+仅应用可调;向平台内全部活跃用户各投递一条通知。
+
+     字段                     类型           必填                                            说明
+                                        是
+is_broadcast           boolean          是                必须为 true
+type                   string           是
+title                  string           是                必须为 NOTIFICATION
+content                string | object  否
+content_type           string           否                标题
+auto_sso               boolean          否
+target_url             string | null    否                正文
+action_url             string | null    否
+action_text            string | null                     默认 ;可选值见 ( 等) TEXT
+                                                                                8.1 USER_NOTIFICATION
+
+                                                         是否对跳转链接做 SSO 封装
+
+                                                         与 配合使用 auto_sso
+
+                                                         自定义跳转(不走 SSO 时)
+
+                                                         按钮文案
+
+约束:不要传 、 ;广播仅支持通知类型。 receiver_id app_user_id
+请求示例
+  POST https://api.hnyunzhu.com/api/v1/messages/ HTTP/1.1
+  Content-Type: application/json
+  X-App-Id: app_demo_001
+  X-Timestamp: 1710000003
+  X-Sign: 5e6f708192a3b4c5d6e7f8090a1b2c3d4e5f678901234567890abcdef0123
+
+  {
+      "is_broadcast": true,
+      "type": "NOTIFICATION",
+      "content_type": "TEXT",
+      "title": "系统维护",
+      "content": "今晚 22:00-24:00 维护"
+
+  }
+
+响应 200 : 返回 MessageResponse ,与「为全体用户各创建一条通知」中的第一条记录对应(便于对接方拿到一条结构化回包);其余用户的消息结构
+相同,仅 id 、 receiver_id 等不同。若平台无活跃用户则返回 400 , detail 为「没有可发送的活跃用户」。
+
+  {
+      "id": 90010,
+      "sender_id": null,
+      "receiver_id": 1001,
+      "app_id": 12,
+      "app_name": "演示应用",
+      "type": "NOTIFICATION",
+      "content_type": "TEXT",
+      "title": "系统维护",
+      "content": "今晚 22:00-24:00 维护",
+      "action_url": null,
+      "action_text": null,
+      "is_read": false,
+      "created_at": "2026-04-07T10:00:00",
+      "read_at": null
+
+  }
+
+成功响应 (发送接口共性) 8.4
+              MessageResponse
+
+     字段            类型                                         说明
+                               消息 ID
+id            int              发送方用户 ID;应用系统通知可能为 null
+sender_id     int | null       接收方用户 ID
+receiver_id   int              应用数据库主键(整数)
+app_id        int | null       应用名称
+app_name      string | null    本文档场景下为 NOTIFICATION (平台尚存在其他类型,见 ) OpenAPI
+type          string           内容类型: 、 、 、 、 TEXT IMAGE VIDEO FILE USER_NOTIFICATION
+content_type  string           标题
+title         string           正文
+content       string           跳转链接(系统通知可能存在)
+action_url    string | null    按钮文案
+action_text   string | null    是否已读
+is_read       bool
+         字段  类型                                            说明
+
+created_at   datetime               创建时间 ISO8601
+
+read_at      datetime | null        已读时间
+
+返回示例( 200 OK ,单条系统通知,与上文发送示例对应):
+
+  {
+      "id": 90001,
+      "sender_id": null,
+      "receiver_id": 1001,
+      "app_id": 12,
+      "app_name": "演示应用",
+      "type": "NOTIFICATION",
+      "content_type": "TEXT",
+      "title": "审批提醒",
+      "content": "您有一条待办审批",
+      "action_url": "/api/v1/simple/sso/jump?app_id=app_demo_001&redirect_to=https%3A%2F%2Foa.example.com%2Fapprove%2F123",
+      "action_text": "去处理",
+      "is_read": false,
+      "created_at": "2026-04-07T10:00:00",
+      "read_at": null
+
+  }
+
+(广播成功时结构相同,通常 receiver_id 为本次作为「代表返回」的那条消息对应的用户 ; ID action_url 、 app_id 等以实际响应为准。)
+
+9. 调试与规范来源
+
+交互式 文档: ( )。 API
+             https://api.hnyunzhu.com/api/v1/docs Swagger
+若本文与线上部署不一致,以实际 OpenAPI 与接口返回为准。
+
+10. 安全要点(摘要)
+
+1. APP_SECRET 仅留在服务端;任何含 Secret 的签名不得在浏览器完成。
+区分 与 。 2.
+         UAP_API_BASE UAP_WEB_BASE
+与 同样属于敏感凭据。 3. APP_ACCESS_TOKEN JWT
+4. SSO 与 Ticket 流程务必使用 HTTPS 与合法回调域。
+
+文档版本:与当前仓库后端 Schema/路由一致时,API 前缀为 /api/v1 。
+

+ 6 - 1
frontend/src/api/apps.ts

@@ -46,6 +46,7 @@ export interface MappingResponse {
   mapped_email?: string
   user_mobile: string
   user_status: string // Added
+  is_active: boolean
   new_user_created?: boolean
   generated_password?: string
 }
@@ -142,7 +143,11 @@ export const createMapping = (appId: number, data: { mobile: string, mapped_key?
   return api.post<MappingResponse>(`/apps/${appId}/mappings`, data)
 }
 
-export const updateMapping = (appId: number, mappingId: number, data: { mapped_key?: string, mapped_email?: string, password: string }) => {
+export const updateMapping = (
+  appId: number,
+  mappingId: number,
+  data: { mapped_key?: string; mapped_email?: string; is_active?: boolean; password: string }
+) => {
   return api.put<MappingResponse>(`/apps/${appId}/mappings/${mappingId}`, data)
 }
 

+ 17 - 1
frontend/src/views/apps/MappingImport.vue

@@ -24,6 +24,13 @@
                 </el-tag>
             </template>
           </el-table-column>
+          <el-table-column prop="is_active" label="映射状态" width="110" align="center">
+            <template #default="scope">
+              <el-tag :type="scope.row.is_active ? 'success' : 'danger'">
+                {{ scope.row.is_active ? '启用' : '禁用' }}
+              </el-tag>
+            </template>
+          </el-table-column>
           <el-table-column prop="mapped_key" label="第三方系统账号 (Key)" />
           <el-table-column prop="mapped_email" label="第三方系统邮箱" />
           <el-table-column label="操作" width="180">
@@ -259,6 +266,9 @@
                           </span>
                           <span v-else-if="scope.row.action_type === 'UPDATE'">
                               变更前: {{ scope.row.details?.old?.mapped_key || '空' }} -> 变更后: {{ scope.row.details?.new?.mapped_key || '空' }}
+                              <span v-if="scope.row.details?.old && scope.row.details?.new && scope.row.details.old.is_active !== scope.row.details.new.is_active">
+                                  · 映射{{ scope.row.details.new.is_active ? '启用' : '停用' }}
+                              </span>
                           </span>
                           <span v-else-if="scope.row.action_type === 'MANUAL_ADD'">
                               新增映射: {{ scope.row.details?.mapped_key }} ({{ scope.row.details?.new_user_created ? '新建用户' : '已有用户' }})
@@ -330,11 +340,14 @@
         <el-form-item label="映射邮箱">
           <el-input v-model="editForm.mapped_email" placeholder="第三方系统邮箱(可选)" />
         </el-form-item>
+        <el-form-item label="映射状态">
+          <el-switch v-model="editForm.is_active" active-text="启用" inactive-text="禁用" />
+        </el-form-item>
         <el-form-item label="管理员密码" required>
           <el-input v-model="editForm.password" type="password" placeholder="请输入您的登录密码确认" show-password />
         </el-form-item>
       </el-form>
-      <template #footer>
+    <template #footer>
         <span class="dialog-footer">
           <el-button @click="editDialogVisible = false">取消</el-button>
           <el-button type="primary" @click="confirmEdit" :loading="editing">确定</el-button>
@@ -542,6 +555,7 @@ const editForm = reactive({
   mobile: '',
   mapped_key: '',
   mapped_email: '',
+  is_active: true,
   password: ''
 })
 
@@ -550,6 +564,7 @@ const handleEdit = (row: MappingResponse) => {
   editForm.mobile = row.user_mobile
   editForm.mapped_key = row.mapped_key
   editForm.mapped_email = row.mapped_email || ''
+  editForm.is_active = row.is_active !== false
   editForm.password = ''
   editDialogVisible.value = true
 }
@@ -564,6 +579,7 @@ const confirmEdit = async () => {
     await updateMapping(appId, editForm.id, {
       mapped_key: editForm.mapped_key || undefined,
       mapped_email: editForm.mapped_email || undefined,
+      is_active: editForm.is_active,
       password: editForm.password
     })
     ElMessage.success('更新成功')