---
description: "Task list for 骑手与用户 IM 即时沟通账号接入"
---
# Tasks: 骑手与用户 IM 即时沟通账号接入
**Input**: Design documents from `/specs/012-im-user-integration/`
**Prerequisites**: plan.md ✅, spec.md ✅, research.md ✅, data-model.md ✅, contracts/ ✅, quickstart.md ✅
**Tests**: 项目无自动化测试约定,不生成测试任务;验证通过 quickstart.md 手动场景完成。
**Organization**: 按用户故事分组。注意:本特性 3 个故事围绕同一接口,存在依赖(US3 配置→US1 接口→US2 幂等),按依赖顺序排列。
## Format: `[ID] [P?] [Story] Description`
- **[P]**: 可并行(不同文件、无未完成依赖)
- **[Story]**: 所属用户故事(US1/US2/US3)
- 描述含确切文件路径
---
## Phase 1: Setup(数据库变更)
**Purpose**: info_user 表新增 IM 凭证两列
- [x] T001 在 `updatesql/sql.md` 追加 info_user 加列 SQL:`ALTER TABLE info_user ADD COLUMN im_api_key VARCHAR(64) DEFAULT NULL COMMENT 'IM平台API密钥';` 与 `ALTER TABLE info_user ADD COLUMN im_user_id BIGINT DEFAULT NULL COMMENT 'IM平台用户ID';`(标注日期 2026-06-23 与用途,不直接执行)
---
## Phase 2: Foundational(阻塞前置,所有故事依赖)
**Purpose**: 实体字段、映射、配置、外部客户端、返回 VO —— 用户故事开始前必须就绪
**⚠️ CRITICAL**: 本相位完成前不得开始用户故事
- [x] T002 [P] `ruoyi-system/src/main/java/com/ruoyi/system/domain/InfoUser.java`:新增 `imApiKey`(String) 与 `imUserId`(Long) 两字段(带 `@Excel` 注解与注释,IM apiKey / IM userId);该类用 Lombok `@Data` 自动生成 getter/setter
- [x] T003 [P] `ruoyi-system/src/main/resources/mapper/infouser/InfoUserMapper.xml`:在 `InfoUserResult` resultMap 追加 `` 与 ``(`selectInfoUserVo` 为 select *,无需改 SQL)
- [x] T004 [P] `ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/ImAccountVo.java`:新建返回 VO,含 `apiKey`(String) 与 `imUserId`(**String**,规避前端 Long 精度丢失);用 `@Data`
- [x] T005 [P] `ruoyi-admin/src/main/resources/application.yml`:新增 `im` 配置段(base-url / ext-token / create-path / timeout-ms),测试值 base-url=`https://test-im.abtim-my.com`、ext-token=`92a88467-6eca-11f1-9dd5-00163e1eec55`、create-path=`/bot/extCreate`、timeout-ms=`5000`(参考现有 ezpay/newebpay 段风格)【满足 US3 配置集中管理】
- [x] T006 [P] `ruoyi-admin/src/main/java/com/ruoyi/app/utils/im/ImClient.java`:新建 `@Component`,`@Value` 注入 `im.base-url`/`im.ext-token`/`im.create-path`/`im.timeout-ms`;方法 `ImAccountVo createAccount()` 用 `org.apache.http`(仿 `NewebPay.postFormRaw`)发 `POST {base-url}{create-path}`,请求头带 `extToken`,设连接/读取超时,解析 fastjson2 响应:`code==200 && data.apiKey/userId 非空` → 填充 VO(imUserId 转 String);否则抛 `ServiceException`【满足 US3 配置注入】
**Checkpoint**: 数据层 + 外部 IM 客户端就绪,可开始用户故事
---
## Phase 3: User Story 1 - APP 触发开通 IM 账号 (Priority: P1) 🎯 MVP
**Goal**: APP 调用 `POST /infouser/user/im/open`,为当前登录用户首次开通 IM 账号并返回凭证
**Independent Test**: 以未开通用户 token 调用接口 → 返回 apiKey/imUserId 且库中两列成对写入(见 quickstart 场景1)
### Implementation for User Story 1
- [x] T007 [US1] `ruoyi-system/src/main/java/com/ruoyi/system/service/IInfoUserService.java`:新增方法签名 `ImAccountVo openImAccount(Long userId);`
- [x] T008 [US1] `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/InfoUserServiceImpl.java`:注入 `ImClient`;实现 `openImAccount` **首次开通**逻辑:`getById(userId)` → (本期暂不做幂等分支,留 US2)→ `imClient.createAccount()` 取 VO → `updateById` 把 apiKey/imUserId 写回用户(成对)→ 返回 VO(注意 imUserId 落库为 Long,VO 用 String)。失败时 ImClient 已抛异常,本层不写库
- [x] T009 [US1] `ruoyi-admin/src/main/java/com/ruoyi/app/user/InfoUserController.java`:新增 `@PostMapping("/im/open")`(复用现有 `JwtUtil`/`request`,仿 NewebPayPayController 行121-129):从 `request.getHeader("token")` 经 `new JwtUtil().getusid(token)` 解析 userId;为空返回未登录错误;否则调 `infoUserService.openImAccount(userId)`,用 `AjaxResult.success(data)` 返回 VO
**Checkpoint**: User Story 1 完整可用 —— 首次开通端到端跑通(幂等性暂缺,由 US2 补齐)
---
## Phase 4: User Story 2 - 重复开通幂等返回 (Priority: P2)
**Goal**: 同一用户重复调用开通接口时,直接返回已有凭证,不重复调用 IM 平台
**Independent Test**: 同一用户连调两次 → 返回凭证一致,IM 侧仅创建一次(见 quickstart 场景2)
### Implementation for User Story 2
- [x] T010 [US2] `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/InfoUserServiceImpl.java`:在 `openImAccount` 开头增加幂等分支——`getById(userId)` 后若 `imApiKey` 非空(已有凭证),直接组装并返回现有 VO(`imUserId` 转 String),**不再调用** `imClient.createAccount()`;否则走 T008 首次开通逻辑
**Checkpoint**: 幂等生效,重复调用安全
---
## Phase 5: User Story 3 - IM 配置集中管理 (Priority: P3)
**Goal**: IM 域名与 extToken 全部来自配置文件,代码无硬编码,环境切换零改码
**Independent Test**: 改 yml 域名/令牌 → 重启 → 调用指向新地址(见 quickstart 场景4 间接验证)
> 实现:US3 由 Foundational T005(yml 配置段)+ T006(@Value 注入)已交付。本相位为合规验证。
- [x] T011 [US3] 验证:grep 确认 `ImClient.java` 与全工程无硬编码 IM 域名(`test-im.abtim-my.com`)或 extToken 明文,均经 `@Value("${im.*}")` 读取;确认 yml 测试/正式环境可仅改配置切换
**Checkpoint**: 配置集中管理达标
---
## Phase 6: Polish & Cross-Cutting Concerns
**Purpose**: 跨故事质量与端到端验证
- [x] T012 [P] 精度复核:确认 `ImAccountVo.imUserId` 为 String,且 controller 返回 JSON 中 imUserId 为字符串(19 位不丢精度);确认 `InfoUser.imUserId` 落库为 Long/BIGINT 不溢出
- [ ] T013 [P] 按 `specs/012-im-user-integration/quickstart.md` 执行场景 1~5 全部验证并记录结果
- [x] T014 [P] 回归确认:用户注册相关接口(骑手/商家/普通用户注册)行为与返回结构未被改动
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: 无依赖,立即开始(T001 仅写 SQL 文件,不执行)
- **Foundational (Phase 2)**: T002~T006 可并行(不同文件);本相位阻塞所有用户故事
- **US1 (Phase 3)**: 依赖 Foundational;内部 T007→T008→T009(接口→实现→控制器)
- **US2 (Phase 4)**: 依赖 US1 的 T008(在已有 service 方法上加幂等分支)
- **US3 (Phase 5)**: 实现已在 Foundational T005/T006;T011 仅验证
- **Polish (Phase 6)**: 依赖全部用户故事完成
### Within Each User Story
- 接口签名 → 实现 → 控制器(US1)
- 幂等分支在首次开通逻辑就绪后追加(US2 依赖 US1)
### Parallel Opportunities
- Foundational T002 / T003 / T004 / T005 / T006 可全部并行(不同文件)
- Polish T012 / T013 / T014 可并行
---
## Implementation Strategy
### MVP First(仅 User Story 1)
1. Phase 1:写 SQL(T001)
2. Phase 2:字段+映射+VO+配置+ImClient(T002~T006)
3. Phase 3:service+controller 首次开通(T007~T009)
4. **STOP 验证**:quickstart 场景1(首次开通)+ 场景5(全类型)
5. 可联调交付
### Incremental Delivery
1. Foundational → 基础就绪
2. +US1 → 首次开通可用(MVP)
3. +US2 → 重复调用幂等(健壮性)
4. +US3 验证 → 配置合规
5. Polish → 精度/回归/全场景
---
## Notes
- 数据库变更只写 `updatesql/sql.md`,不直接执行(项目规范)
- `imUserId` 在 VO 中用 String,落库用 Long/BIGINT(防前端 JS 精度丢失 + 容纳 19 位)
- 复用现有 `JwtUtil.getusid(token)` 解析用户,不新增鉴权方式
- 不改动注册流程(用户明确要求)