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 幂等),按依赖顺序排列。
[ID] [P?] [Story] DescriptionPurpose: info_user 表新增 IM 凭证两列
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 与用途,不直接执行)Purpose: 实体字段、映射、配置、外部客户端、返回 VO —— 用户故事开始前必须就绪
⚠️ CRITICAL: 本相位完成前不得开始用户故事
ruoyi-system/src/main/java/com/ruoyi/system/domain/InfoUser.java:新增 imApiKey(String) 与 imUserId(Long) 两字段(带 @Excel 注解与注释,IM apiKey / IM userId);该类用 Lombok @Data 自动生成 getter/setterruoyi-system/src/main/resources/mapper/infouser/InfoUserMapper.xml:在 InfoUserResult resultMap 追加 <result property="imApiKey" column="im_api_key"/> 与 <result property="imUserId" column="im_user_id"/>(selectInfoUserVo 为 select *,无需改 SQL)ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/ImAccountVo.java:新建返回 VO,含 apiKey(String) 与 imUserId(String,规避前端 Long 精度丢失);用 @Dataruoyi-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 配置集中管理】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 客户端就绪,可开始用户故事
Goal: APP 调用 POST /infouser/user/im/open,为当前登录用户首次开通 IM 账号并返回凭证
Independent Test: 以未开通用户 token 调用接口 → 返回 apiKey/imUserId 且库中两列成对写入(见 quickstart 场景1)
ruoyi-system/src/main/java/com/ruoyi/system/service/IInfoUserService.java:新增方法签名 ImAccountVo openImAccount(Long userId);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 已抛异常,本层不写库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) 返回 VOCheckpoint: User Story 1 完整可用 —— 首次开通端到端跑通(幂等性暂缺,由 US2 补齐)
Goal: 同一用户重复调用开通接口时,直接返回已有凭证,不重复调用 IM 平台
Independent Test: 同一用户连调两次 → 返回凭证一致,IM 侧仅创建一次(见 quickstart 场景2)
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/InfoUserServiceImpl.java:在 openImAccount 开头增加幂等分支——getById(userId) 后若 imApiKey 非空(已有凭证),直接组装并返回现有 VO(imUserId 转 String),不再调用 imClient.createAccount();否则走 T008 首次开通逻辑Checkpoint: 幂等生效,重复调用安全
Goal: IM 域名与 extToken 全部来自配置文件,代码无硬编码,环境切换零改码
Independent Test: 改 yml 域名/令牌 → 重启 → 调用指向新地址(见 quickstart 场景4 间接验证)
实现:US3 由 Foundational T005(yml 配置段)+ T006(@Value 注入)已交付。本相位为合规验证。
ImClient.java 与全工程无硬编码 IM 域名(test-im.abtim-my.com)或 extToken 明文,均经 @Value("${im.*}") 读取;确认 yml 测试/正式环境可仅改配置切换Checkpoint: 配置集中管理达标
Purpose: 跨故事质量与端到端验证
ImAccountVo.imUserId 为 String,且 controller 返回 JSON 中 imUserId 为字符串(19 位不丢精度);确认 InfoUser.imUserId 落库为 Long/BIGINT 不溢出specs/012-im-user-integration/quickstart.md 执行场景 1~5 全部验证并记录结果updatesql/sql.md,不直接执行(项目规范)imUserId 在 VO 中用 String,落库用 Long/BIGINT(防前端 JS 精度丢失 + 容纳 19 位)JwtUtil.getusid(token) 解析用户,不新增鉴权方式