ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Java 项目实战:交友项目 - 用户体系集成总结

Java 项目实战:交友项目 - 用户体系集成总结

概述

到这篇为止,「用户体系集成」这个小系列正式收官。它解决的是项目接环信的第一个必答题:自己的用户和环信的用户怎么对得上号。这个问题看起来简单,实际却牵动注册、登录、鉴权、好友、消息五条链路,任何一环没对齐,用户就会遇到「能登录但收不到消息」「好友列表两边不一样」这类难以排查的故障。

这篇不重复细节,只做四件事:把整条链路串成一张闭环图,让读者一眼看清各环节的先后依赖;把可复用的套路和踩过的坑沉淀成清单,方便日后接其他第三方服务时直接照搬;给出上线前的自检表,把「感觉没问题」变成「逐项验证过」;最后留几道课后练习,把课程代码里没做完的补偿、幂等、对账补上。读完之后,你应该能独立判断一个新接入的第三方用户体系该怎么映射、凭证该怎么下发、双写该怎么兜底。

纲要

  • 系列回顾:四块拼图组成的账号映射闭环
  • 知识地图(Mermaid 全链路图)
  • 各篇分工与代码落点(表格)
  • 可复用套路清单(这里核心)
  • 双体系账号映射:前缀映射 vs 映射表
  • 凭证下发接口的三条铁律
  • 好友关系双写与一致性代价
  • 第三方能力四件套(环信是第五次应用)
  • 分层验证路径
  • 关键坑回顾(10 条浓缩表)
  • 常见误解澄清
  • 上线前自检清单(可勾选)
  • 练习建议(每项:改哪里 / 怎么验证)
  • 下一章预告:附近的人

系列回顾:一条账号映射闭环

一句话概括整个系列:把项目用户体系与环信打通,形成正向注册(userId → hx+id)+ 凭证下发(App 登录环信)+ 反向反查(hx+id → userId)+ 好友双写(MongoDB friend 集合 + 环信联系人)的完整账号映射闭环。

这四个环节不是并列关系,而是有严格先后依赖的链条。少了任何一环,聊天都跑不通:没注册,App 登不进环信,SDK 连不上长连接;没有凭证接口,App 不知道用什么账号密码登录,只能把凭证硬编码在客户端,等于把安全边界交给用户;没有反查,收到消息只知道对方是hx1001,却不知道对应项目里的哪个用户,头像昵称都展示不出来;没有双写,本地好友列表和环信联系人各说各话,A 看到 B 是好友、B 却看不到 A。

理解这条依赖链的价值在于排障:出问题时按链条顺序从上游往下游查,先确认注册成功,再确认凭证下发正确,再确认反查能命中,最后确认双写一致,比盲目翻日志快得多。

回调/通知场景

补齐展示信息

注册项目
手机号+验证码

同步注册环信
hx+userId

凭证下发
GET /huanxin/user 回库取

App 环信登录
SDK 长连接

加好友双写
MongoDB friend 集合 + 环信联系人

聊天直达
消息走环信通道

反向反查
hx_user → userId

各环节的代码落点与本系列篇目对应关系:

环节代码落点对应篇目
正向注册(新用户)UserService#loginVerification注册分支将新用户注册到环信
正向注册(存量用户)HuanxinTest#register批量刷库环信后台发送消息测试
凭证下发HuanXinController+HuanXinService查询用户环信账号密码
反向反查MessagesController#userinfo+UserApi#findByHuanxin根据环信 ID 反查用户
好友双写HuanXinTemplate#addContact+ 好友表联系人管理(后一章衔接)

工程完成后的形态是:User表新增hxUser/hxPassword两个字段,Constants里落了两个常量,这两个常量值得单独记住:

// mf-commons/.../Constants.javapublicstaticfinalStringINIT_PASSWORD="123456";publicstaticfinalStringHX_USER_PREFIX="hx";

映射规则就一行:环信 ID =HX_USER_PREFIX + userId,即hx1001。所有正向、反向逻辑都锚定这一条规则,这也是整个系列最值得记住的一句话。

这条规则的好处是「无状态」:不需要额外查表就能从 userId 推出环信 ID,也能从环信 ID 反推出 userId,正反两个方向都是纯计算。代价是规则一旦定下就不能随意改,因为已经注册到环信的账号名是固定的,改前缀意味着存量用户全部对不上号。所以前缀值要当成契约对待,写进常量、禁止各处手写字符串拼接,这也是坑表里第 8 条强调「统一走HX_USER_PREFIX + id」的原因。

可复用套路清单

这一节是全系列真正能带走的东西。每个套路都标注了在本系列怎么用的、后续哪些地方还会遇到。

套路本系列怎么用后续哪里用
双体系账号映射自有 userId 与环信 username 的绑定,用"hx" + userId前缀规则 +hxUser字段落库双保险接入任何第三方用户体系(支付实名、推送、客服系统)都会遇到同一问题
凭证下发接口GET /huanxin/user,JWT 鉴权后按UserHolder.getUserId()回库取,只返当前用户自己的凭证OAuth 场景下发临时凭证、App 换取推送 token,套路完全一致
好友关系双写好友申请通过后,本地 friend 集合和环信联系人各写一份任何「本地状态 + 第三方状态」的组合(本地订单 + 物流平台、本地群组 + IM 群)
第三方能力四件套环信是「配置类 → Properties → Template → 自动配置」的第五次应用(前面:OSS、OSS 阿里云、短信、百度人脸)后续所有第三方组件(MongoDB、青乌云透等)继续复用
分层验证Console 手工发消息 → REST 接口验证注册 → 业务消息端到端任何外部服务集成的排障路径
双体系账号映射展开说一句。前缀映射("hx" + userId)和映射表(独立表存userId ↔ thirdId)是两种典型方案:
维度前缀映射映射表
实现成本零成本,规则即映射多一张表、多一组 CRUD
可读性高,看到 ID 就知道是谁需查表
灵活性差,第三方 ID 必须允许自定义且遵循规则好,第三方自动生成 ID 也能接
本系列的选择环信允许自定义 username,选它—

环信恰好允许自定义 username,所以前缀映射是最省事的选型;如果接的服务只给你返回一个不可控的 UUID,就只能老老实实建映射表。选型前先确认对方的 ID 是否可控,这一步问清楚能省掉后面大量返工。

还有一个折中方案值得知道:前缀映射 + 落库双保险。平时靠规则计算,同时在User表里存一份hxUser字段,反查时优先读字段、字段为空才回退到规则计算。这样既保留了零成本的优势,又能在规则变更或历史数据异常时留一条退路,本系列采用的正是这种写法。
凭证下发接口的三条铁律,HuanXinService的实现就是范本:

publicHuanXinUserVofindHuanXinUser(){LonguserId=UserHolder.getUserId();// 铁律一:userId 只能从 token 解出,绝不接收前端传参Useruser=userApi.findById(userId);if(user==null){returnnull;}returnnewHuanXinUserVo(user.getHxUser(),user.getHxPassword());// 铁律二:只返当前用户自己的凭证// 铁律三:生产环境密码不能这样明文直返,应换成环信用户级 token}

关于好友双写的一致性代价:实际代码顺序是先环信addContact、后本地friendApi.save(见「联系人管理」一节),所以可能的不一致是「环信成功、本地save失败」——此时项目的处理只是记日志返回 false,不回滚环信。这个取舍在课程项目里可接受,但要想清楚,双写意味着强一致换不回来,只能靠对账兜底。后面的练习里有对应任务。

为什么不能靠回滚解决?因为环信是外部服务,本地事务回滚管不到它,而调用环信删除联系人又是一次可能失败的网络请求,回滚本身也会失败,最终还是要靠对账。所以双写场景的正确心态是:接受短暂不一致,用定时对账把差异收敛,而不是追求写入瞬间的强一致。对账的方向也要定好,本系列以本地为准,发现环信缺失就补addContact,反过来则不做自动删除,避免误删用户手工添加的联系人。

分层验证也值得复盘成套路:环信 Console 手工发消息验证通道 → REST 调环信 API 验证注册 → App 端真实收发验证端到端。自下而上,每层排除了下层的问题,出问题才知道卡在哪一层。这套顺序的价值在于缩小排查范围:如果 Console 都发不出消息,说明是环信侧配置或网络问题,根本不用看业务代码;如果 Console 正常而 REST 注册失败,问题就在服务端调用;如果前两层都正常但 App 收不到,才轮到客户端 SDK 和长连接。

关键坑回顾

整个系列浓缩成一张坑表,每一条都在前面某篇里真实出现过:

#坑后果与成因规避
1环信注册失败被吞createUsercatch 住只记日志返回 false,项目注册成功、环信失败的不一致无人补偿,该用户永远收不到消息失败入重试队列或惰性注册兜底(见练习)
2重复 createUser 报「已存在」也返回 false异常被当成失败,hxUser回写被跳过,用户实际已存在但库里是空的已存在视为成功,幂等处理(见练习)
3INIT_PASSWORD 弱凭证全员123456,拿到他人 hxUser 即可冒充登录生产换环信用户级 token 或随机密码
4凭证接口不鉴权/huanxin/user若裸奔,遍历 hx+id 可拖走全员环信凭证JWT 必过拦截器,userId 取自 token
5环信 ID 大小写HuanXinTemplate里username.toLowerCase(),项目侧若不统一大小写,回查对不上两侧统一小写,或都走hxUser字段不拼规则
6好友双写不一致环信成功本地失败(或反之),好友列表两边对不齐对账任务 + 以本地为准做补偿
7聊天记录在环信手里消息数据边界在第三方,本地库没有聊天记录,审计/检索受制于人需要留存就走环信回调落库
8username 未加前缀测试代码里createUser("106","123456")漏了hx前缀,环信侧出现游离账号,反查永远失败统一走HX_USER_PREFIX + id拼接,禁止手写
9响应式.block()的位置.block()封装在 Template 内部,业务层拿到的是同步 Boolean;若漏封,业务线程会被响应式订阅卡住响应式 API 统一在组件层收口
10双 token 体系混淆项目 JWT 与环信凭证各管各的,拿 JWT 去登环信或拿环信凭证调项目接口都会失败记住各自通道:REST 走 JWT,SDK 走环信凭证

常见误解澄清

误解实际情况
「环信会自动同步我的用户」不会。环信是独立账号体系,项目用户必须显式调 API 注册,一个都不能少
「用户注册环信是登录时才做的」项目的设计是首次登录即注册(新用户在loginVerification里落库后立刻注册环信),存量用户靠批量脚本补
「批量注册随便跑」测试账号环信侧最多 100 个用户,超了会失败;跑之前先清库清后台
「凭证接口可以顺带查别人的环信账号」只能查自己的,userId 从 JWT 解出,这是安全边界不是功能限制
「聊天记录存在项目数据库里」不在。消息收发与存储都在环信,本地只有用户与好友关系
「App 拿项目 token 也能登环信」两套凭证互不相认。JWT 登 REST,环信凭证登 SDK,缺一不可

上线前自检清单

发布前照着勾,每项都给出验证方法:

  • 新用户自动注册环信:新手机号登录后,查库hx_user非空,环信 Console 能搜到该用户
  • 凭证接口带 JWT 返回正确:GET /huanxin/user携带 token 返回当前用户凭证;不带 token 返回 401
  • 无前缀/他人 ID 被拒绝:接口不接收任何用户名参数,传入他人 userId 无法获取其凭证
  • 加好友双写成功:好友通过后本地 friend 集合有记录,环信 Console 联系人列表同步出现
  • 聊天消息 App 可收:Console 以对方身份发文本消息,App 端长连接能收到
  • 环信 ID 反查正确:GET /messages/userinfo?huanxinId=hx106返回 userId=106 的用户详情
  • 失败可感知:手工断网环信再注册新用户,日志出现「创建环信用户失败」且后续有补偿路径(依赖练习落地)

练习建议

四个技术任务,都比课程代码多走一步,直接对着「改哪里 / 怎么验证」做:

  1. 环信注册失败的补偿
  • 改哪里:createUser返回 false 时,把userId写入 Redis 的重试队列(或一张hx_register_retry表),定时任务扫队列重新调huanXinTemplate.createUser;再加一层「惰性注册」兜底,凭证接口发现hxUser为空时现场补注册并回写。
  • 怎么验证:手工把环信侧用户删掉、hxUser置空,模拟注册失败后触发重试,最终库里有hxUser、环信 Console 有账号。
  1. createUser 幂等处理
  • 改哪里:HuanXinTemplate#createUser捕获异常时判断错误信息是否为「用户已存在」(或先调service.user().get(username)探测),已存在直接返回 true。
  • 怎么验证:对同一hx+id连调两次createUser,第二次仍返回 true,hxUser回写不被跳过。
  1. 凭证接口改用户级 token 方案
  • 改哪里:HuanXinService#findHuanXinUser不再返回密码,改为调环信「获取用户 token」API(服务端以管理员身份为指定用户换取 token),HuanXinUserVo的password字段换成token+ 有效期;App 端 SDK 改用 token 登录。
  • 怎么验证:接口响应里不再出现123456,App 用下发的 token 能正常登录环信并收发消息。
  1. 好友双写的一致性对账
  • 改哪里:写一个定时任务(或测试类),分页遍历本地 friend 集合,对每对好友调环信 API 查询联系人关系,缺失的调addContact补齐并记对账日志。
  • 怎么验证:手工在环信 Console 删掉一条联系人关系,跑对账任务后关系恢复,日志输出修补明细。

下一章预告

用户体系打通之后,IM 的地基就算打完了,下一章转向「附近的人」,用 MongoDB 的地理位置能力(2dsphere索引 + 附近查询)实现同城推荐。好消息是前面章节的movement(动态)表早就预留了经纬度字段,到时候直接在其上建索引就能查,不用改表。

总结

  • 用户体系集成本质是账号映射:一条"hx" + userId规则贯穿正向注册、凭证下发、反向反查三个方向,闭环缺一环聊天就不通。
  • 凭证下发接口的安全三铁律(token 取 id、只返自己、生产换 token)比接口本身更值得带走。
  • 双写带来的一致性问题课程代码没有解决,补偿、幂等、对账是三个标准的补课方向,练习里已给出落点。
  • 环信是「第三方能力四件套」的第五次应用,这个装配套路此后每个新组件都在重复,越用越熟。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进