# Feature Specification: 骑手与用户 IM 即时沟通账号接入

**Feature Branch**: `012-im-user-integration`

**Created**: 2026-06-23

**Status**: Draft

**Input**: User description: "骑手和用户沟通要使用 im 功能，我们需要调用 im 的功能，让 im 给我们创建用户；用户表绑定 im 创建用户返回的信息；创建用户调用方法 POST https://test-im.abtim-my.com/bot/extCreate，请求头带 extToken；返回 apiKey 与 userId；im 域名和 extToken 放到配置文件。用户创建过程不改动，APP 在用户创建完成后自己触发接口，后端再去调用 im 方法创建 im 账号。"

## User Scenarios & Testing *(mandatory)*

### User Story 1 - APP 触发开通 IM 账号 (Priority: P1)

用户在平台注册完成后，APP 主动调用平台提供的「开通 IM 账号」接口。平台后端识别当前登录用户，向 IM 平台发起创建请求，将 IM 平台返回的 apiKey 与 userId 绑定保存到该用户记录中，并把这两个凭证返回给 APP，供 APP 初始化 IM SDK 进行即时沟通。

**Why this priority**: 这是整个 IM 沟通能力的根基——没有 IM 账号就无法收发任何消息。它是 MVP 的核心切片，其他故事都依赖它。

**Independent Test**: 可通过"以某用户身份调用开通接口 → 校验用户表 imApiKey / imUserId 已写入 → 校验接口返回了这两个凭证 → IM 平台确实存在该账号"独立验证，交付价值为"用户已具备 IM 身份并可直接聊天"。

**Acceptance Scenarios**:

1. **Given** 一个已登录、尚未开通 IM 账号的用户，**When** APP 调用「开通 IM 账号」接口，**Then** 平台成功调用 IM 平台创建账号，用户表 imApiKey / imUserId 被写入非空值，接口返回这两个凭证。
2. **Given** IM 平台返回成功（code=200），**When** 平台处理响应，**Then** 仅从 data 节点取出 apiKey 与 userId 落库并返回。
3. **Given** IM 平台临时不可用或返回失败，**When** APP 调用开通接口，**Then** 接口返回明确的失败信息（不写库），APP 可稍后重试。

---

### User Story 2 - 重复开通幂等返回 (Priority: P2)

同一个用户多次调用「开通 IM 账号」接口时，平台不重复在 IM 平台创建账号，而是直接返回该用户已有的 IM 凭证，保证幂等，避免产生重复 IM 账号。

**Why this priority**: 幂等是接口健壮性的核心保障，避免 APP 重试或网络抖动导致一个用户绑定多个 IM 账号；优先级仅次于首次开通。

**Independent Test**: 可通过"对同一用户连续调用两次开通接口 → 确认 IM 平台只创建一次、返回的凭证一致"独立验证。

**Acceptance Scenarios**:

1. **Given** 某用户 imApiKey 已存在，**When** APP 再次调用开通接口，**Then** 平台直接返回现有凭证，不再次调用 IM 平台创建。
2. **Given** APP 因网络问题重复提交，**When** 并发或连续到达，**Then** 该用户最终只拥有一组 IM 凭证。

---

### User Story 3 - IM 配置集中管理 (Priority: P3)

IM 平台的访问域名与鉴权令牌（extToken）集中存放于系统配置文件中，由配置统一管理，代码中不硬编码任何 IM 域名或令牌，便于测试环境与正式环境切换。

**Why this priority**: 支撑性需求，是 P1/P2 正确、安全运行的前提，但属于工程化而非用户可见功能。

**Independent Test**: 可通过"修改配置文件中的域名/令牌 → 重启 → 确认调用指向新地址"独立验证。

**Acceptance Scenarios**:

1. **Given** 配置文件已配置 IM 域名与 extToken，**When** 系统启动，**Then** 平台读取并使用配置值调用 IM 平台，代码内不出现硬编码域名或令牌。
2. **Given** 需要从测试环境切换到正式环境，**When** 仅修改配置文件，**Then** 无需改动代码即可完成切换。

---

### Edge Cases

- IM 平台返回 userId 超出普通整数范围（如 19 位长整型）时，存储字段类型如何避免精度丢失？
- APP 短时间内多次请求开通接口时，系统如何保证幂等（不重复创建、不覆盖已有有效账号）？
- IM 平台返回 code 非 200（鉴权失败 / 限流 / 服务异常）时，系统如何明确返回失败，便于 APP 重试？
- IM 平台调用超时（如 > 5 秒）时，是否阻塞接口响应？超时阈值与重试策略是什么？
- extToken 泄露或过期，系统如何感知与处理？
- 未登录或 token 失效调用开通接口，系统应拒绝（不产生账号）。
- 用户删除后，IM 账号是否需要同步注销？（本期范围外，仅记录）

## Requirements *(mandatory)*

### Functional Requirements

- **FR-001**: 系统 MUST 提供一个独立的「开通 IM 账号」接口，供 APP 在用户注册完成后（或首次需要沟通时）主动调用；用户注册主流程保持不变，不在注册环节内嵌 IM 开通。
- **FR-002**: 系统 MUST 通过当前登录 token 解析出 userId 来识别要开通的用户，不接受由 APP 显式传入 userId（防止越权为他人开通）。
- **FR-003**: 系统 MUST 调用 IM 平台创建账号（POST {IM域名}/bot/extCreate，请求头带 extToken），将 IM 平台返回的 apiKey 与 userId 绑定保存到用户表对应字段。
- **FR-004**: 系统 MUST 通过请求头 `extToken` 携带鉴权令牌，令牌值与 IM 域名均从配置文件读取，代码中不硬编码。
- **FR-005**: 系统 MUST 正确解析 IM 平台返回结构（外层 code/message，内层 data 含 apiKey/userId），仅取 data 节点落库。
- **FR-006**: 系统 MUST 在开通成功后将 apiKey 与 imUserId 返回给 APP，供 APP 初始化 IM SDK。
- **FR-007**: 接口 MUST 对所有用户类型开放（普通用户 0 / 商家 1 / 骑手 2 / 夜市 3），由 APP 决定调用方；后端不按用户类型限制。
- **FR-008**: 系统 MUST 保证幂等——当目标用户已存在 IM 凭证（imApiKey 非空）时，直接返回现有凭证，不再调用 IM 平台创建。
- **FR-009**: 当 IM 平台不可用、返回失败或调用超时时，系统 MUST 返回明确的失败结果，且不在用户表写入无效凭证。

### Key Entities *(include if feature involves data)*

- **InfoUser（用户信息）**: 平台已有用户表。本期新增两个 IM 凭证字段：imApiKey（IM 平台返回的 API 密钥，字符串）与 imUserId（IM 平台返回的用户ID，长整型 BIGINT）。一个用户对应一组 IM 凭证。
- **IM 平台外部账号**: 由 IM 平台管理，通过 extCreate 接口创建，对平台而言是不可变的对外凭证，以 apiKey+userId 形式回传绑定。

## Success Criteria *(mandatory)*

### Measurable Outcomes

- **SC-001**: 100% 的「开通 IM 账号」接口调用（IM 平台可用时）成功为用户写入并返回 IM 凭证。
- **SC-002**: 对同一用户重复调用开通接口，IM 平台账号创建次数为 1，重复请求 100% 命中幂等返回。
- **SC-003**: 未登录或 token 失效调用开通接口时，100% 被拒绝，不产生任何 IM 账号或写库。
- **SC-004**: IM 创建请求平均耗时控制在可接受范围，接口响应不被显著拖慢（如额外开销 < 1 秒）。
- **SC-005**: 测试环境与正式环境的 IM 域名/令牌切换，仅需修改配置文件，零代码改动。

## Assumptions

- 用户注册主流程（骑手注册、商家注册、普通用户注册/登录即注册）保持原样，本期完全不动注册相关代码，仅在注册完成后由 APP 自行调用开通接口。
- IM 平台创建账号接口（extCreate）对同一外部用户具备可接受的重复请求处理；系统侧额外通过「凭证已存在则直接返回」做幂等保护。
- IM 平台返回的 userId 为长整型，存储字段按 BIGINT 设计以避免精度丢失。
- extToken 为平台级共享令牌（非单用户级），所有创建请求共用同一令牌。
- 接口通过项目现有登录鉴权机制识别当前用户（解析 token → userId），不新增鉴权方式。
- 配置文件采用项目现有 application.yml 风格（参考 ezpay / newebpay 段），新增独立 im 配置段。
- HTTP 客户端沿用项目现有 org.apache.http 用法（参考 NewebPay 工具类）。
