ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

uniapp集成融云IM实现聊天与音视频通话完整指南

uniapp集成融云IM实现聊天与音视频通话完整指南 简介一份面向uniapp开发者的融云IM集成资源完整覆盖单聊、群聊及单/多人音视频通话场景适合需要快速在跨端应用中接入即时通讯与呼叫能力的中高级前端或移动端开发者。配套文档包含后端token获取与maven环境搭建说明并有可直接运行的demo工程具体涉及消息监听、消息撤回回执、分页拉取聊天记录、会话未读总数与单会话未读数、免打扰时段设置、输入状态消息以及多路音视频通话等接口实现。资源包共651个文件约79.12MB以png图片素材、h头文件、js脚本、plist配置及aar/静态库等组件构成同时包含apk/ipa可安装包与vue/nvue页面文件便于对照源码理解原生依赖与前端交互逻辑。已有2636人学习下载对希望快速跑通融云IM全流程并了解Android/iOS底层库集成的开发者具有较高参考价值。 上个月刚把一个基于uniapp的社交类项目跑通聊天、群聊、音视频通话都集成了融云IM后端用Java搭的token服务Maven管理依赖还整理了一份带demo的完整文档。这阵子陆续有人问我“融云在uniapp里怎么接”“token怎么动态获取”“多人通话怎么实现”问的人多了索性把整个对接过程、踩坑点、核心代码全都梳理成一篇给后面要做IM/音视频方向的朋友做个参考。这篇文章适合三类人一是uniapp前端开发者想给App或小程序塞进IM能力二是后端同学需要搭建融云token服务和签名机制配合Maven环境三是想快速跑通一个“单聊群聊音视频”demo的团队或个人。整个项目用到的核心链路是客户端uniapp拿到后端动态签发的token → 调用融云connect建立长连接 → 收发单聊/群聊消息 → 通过融云RTC组件发起或加入音视频通话。1. 项目整体思路与方案选型1.1 为什么选融云IM而不是自己写聊天很多人第一反应是“IM不就是WebSocket发消息吗自己写不就行了”。真有这个想法的一般是还没踩过坑。聊天系统的难点从来不在“发一条消息”而在消息可靠性、多端同步、离线推送、群成员管理、未读计数、历史消息拉取、消息已读回执还有音视频通话的信令协商、网络穿透、弱网切换。这些全自己做一个5人团队至少得投入半年做出来还不一定稳定。融云这类IM云服务把最难的通信底层全包了客户端SDK负责长连接和数据同步服务端SDK负责token、用户体系、消息路由。我把精力放在业务层比如好友关系、社群运营、消息内容的业务处理上比从零造轮子划算得多。凡是App里直接内嵌聊天界面第一优先级都是接这种成熟的IM PaaS。1.2 整体技术架构前端、服务端和融云云的三角关系一个标准的融云接入项目技术链路是这样的客户端uniapp引入融云SDK首次启动时向后端服务器请求token拿到token后调用SDK的connect接口建立长连接。服务端Java Maven负责调用融云服务端API生成token持有融云AppKey和AppSecret绝不能把这两个密钥放到客户端代码里。融云云端负责维护长连接通道、消息存储、群组信息、音视频信令转发。这里有个特别容易理解错的地方融云IM不是让客户端直接拿AppKey和AppSecret去连客户端只需要一个token这个token由你的后端向融云服务端申请然后下发给客户端。token跟具体用户绑定一个用户在同一时间只会有一个有效token重复获取会让之前的token失效。这个机制保证了消息通道的安全也意味着token必须是后端签发的不能由客户端自己拼接。1.3 为什么Maven环境是这个项目的地基项目的后端服务采用Java技术栈那么Maven就是集成融云Java SDK最顺手的方式。Maven把融云SDK的依赖声明写进pom.xml它会自动拉取融云相关的jar包以及这些jar包依赖的其他第三方库。没有Maven的话你得手动下载融云jar包、融云依赖的httpclient等还要自己处理包冲突版本稍微不对运行期就是各种NoSuchMethodError或者ClassNotFoundException。我在这套项目里用的Spring Boot 2.7 Maven 3.8组合融云Java SDK通过中央仓库坐标引入一条依赖就搞定所有jar包传递。这也是我把“Maven环境”单独写进项目标题的原因——后端项目如果Maven环境没搭好token接口根本跑不起来。2. 后端Token服务搭建Maven环境与核心代码2.1 本地Maven环境怎么配置最省事Maven本身的安装不复杂下载二进制包解压配置环境变量关键是国内网络环境下要改镜像源否则下载依赖能等到怀疑人生。我用的阿里云公共仓库镜像直接在~/.m2/settings.xml里配置mirrors mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror /mirrorsIDEA里的Maven配置也建议统一指向本地settings.xml这样命令行和IDEA里用的是同一份配置避免两套环境拉下来的依赖不一致。这一步看着基础但凡是“我IDEA里能跑、命令行就报错”的诡异情况十有八九就是IDEA用了内置Maven但命令行用的是另外一套。2.2 pom.xml引入融云服务端SDK在Spring Boot的pom.xml里加入融云SDK依赖dependency groupIdcn.rongcloud.sdk/groupId artifactIdsdk-core/artifactId version1.2.4/version /dependency需要注意的是融云服务端SDK一直在迭代不同版本的包名和类名会有差异。老版本用的是io.rong.RongCloud新版本部分模块包结构调整过引入前先看融云官方文档的“服务端SDK下载”页面确认版本号。项目中的示例代码以cn.rongcloud.sdk为例如果你用的是别的版本第一步就是先把包名和引入路径对齐不然后面全是红叉。2.3 生成Token的后端接口实现整个后端服务其实就一个核心接口根据userId和用户信息返回token。代码逻辑很直接RestController RequestMapping(/api/im) public class ImTokenController { Value(${rongcloud.app-key}) private String appKey; Value(${rongcloud.app-secret}) private String appSecret; PostMapping(/token) public MapString, Object getToken(RequestBody TokenRequest request) { // 初始化融云服务端SDK RongCloud rongCloud RongCloud.getInstance(appKey, appSecret); // 组装用户信息userId在融云体系里是用户的唯一标识 User user new User(); user.setId(request.getUserId()); user.setName(request.getUserName()); user.setPortrait(request.getUserPortrait()); // 调用服务端接口获取token TokenResult result rongCloud.user().getToken(user.getId(), user.getName(), user.getPortrait()); MapString, Object resp new HashMap(); if (result.getCode() 200) { resp.put(code, 200); resp.put(token, result.getToken()); } else { resp.put(code, result.getCode()); resp.put(msg, result.getErrorMessage()); } return resp; } }这个接口必须在你的业务服务器上部署而且要用HTTPS。调用融云服务端API时服务端会校验AppKey和AppSecret的签名这两项配置放在application.yml或环境变量里别写死在代码里rongcloud: app-key: 你的AppKey app-secret: 你的AppSecret2.4 为什么token不能在前端生成这个问题几乎每次都会被问到。融云的连接机制是客户端SDK持有一个token拿token去融云服务器换取连接授权。如果前端自己能调用接口拿token那等同于前端持有AppSecret任何用户都能通过反编译或抓包拿到你的密钥就能冒充任意用户发消息。所以token签发必须在你自己的后端完成AppKey可以暴露给客户端AppSecret只能存在于服务端。token的有效期也需要注意。融云官方说明token长期有效但如果你调用了“刷新用户信息”接口旧的token会立即失效。因此我在后端做了一层缓存用户第一次请求token时生成并缓存后续请求只查缓存不重新调融云接口除非主动刷新用户信息。这样既避开了token被反复签发导致互相顶掉的问题也减少了服务端API调用量。3. uniapp客户端集成从初始化到单聊群聊3.1 manifest.json配置要点uniapp端的嵌工作最先要搞定的是manifest.json。这块不做好后面运行到微信开发者工具或真机上就是各种白屏、报错、权限不生效。App平台需要勾选“IM模块”并填入融云的AppKey。具体路径是manifest.json的App模块配置里找到“融云IM”或“Cloud”相关配置项。如果用的是HBuilder X直接在可视化界面勾选模块并填AppKey即可。iOS和Android都要配置摄像头、麦克风权限描述用于音视频通话。iOS的权限描述如果不填真机调用摄像头时会直接崩溃。同时iOS上架App Store强制要求隐私政策弹窗如果用户不同意隐私政策需要退出App。我一般在App.vue的onLaunch里做这个逻辑onLaunch() { // 假设这里调用了一个检查用户隐私同意状态的方法 if (!getPrivacyAgreeStatus()) { // 弹窗提示用户阅读隐私政策 uni.showModal({ title: 提示, content: 需要您同意隐私政策后才能继续使用, confirmText: 同意, cancelText: 不同意, success(res) { if (res.confirm) { setPrivacyAgreeStatus(true); } else { // 用户不同意退出App plus.runtime.quit(); } } }); } }微信小程序平台则要注意融云的SDK在微信小程序里有专门的小程序版本跟App版是两套。如果你同时要发布到App和小程序得在代码里做平台判断加载不同的SDK入口。3.2 SDK引入与初始化连接uniapp项目引入融云SDK我用的方式是npm包rongcloud/cloud-core配合官方提供的uniapp插件市场里的IM插件。安装完成之后核心的连接流程如下// 封装的IM连接模块 im.js import { RongIMClient } from rongcloud/cloud-core; // 初始化SDKAppKey从manifest配置里读取或全局常量获取 RongIMClient.init(你的AppKey); function connectIM() { return new Promise((resolve, reject) { // 先从自己的后端获取token uni.request({ url: https://你的后端域名/api/im/token, method: POST, data: { userId: currentUserId, userName: currentUserName, userPortrait: currentUserAvatar }, success(res) { const token res.data.token; // 用token建立融云连接 RongIMClient.connect(token, { onSuccess(userId) { console.log(连接成功, userId); resolve(userId); }, onTokenIncorrect() { console.log(token无效需要重新获取); reject(new Error(token无效)); }, onError(error) { console.log(连接失败, error); reject(error); } }); } }); }); }连接成功之后SDK会自动管理长连接的重连、心跳、消息同步这些都不用自己处理。App前后台切换时SDK也会自动处理连接状态恢复。3.3 单聊与群聊消息收发发消息和收消息是聊天最基础的能力。融云SDK的消息对象用ConversationType区分单聊和群聊PRIVATE是单聊GROUP是群聊。我封装了一套统一的发送方法import { RongIMClient, MessageType } from rongcloud/cloud-core; // 发送文本消息 function sendTextMessage(conversationType, targetId, contentText) { const conversation { conversationType: conversationType, // ConversationType.PRIVATE 或 GROUP targetId: targetId, // 对方用户id或群组id content: { messageType: MessageType.TEXT, content: contentText } }; RongIMClient.getInstance() .sendMessage(conversation, { onSuccess(message) { console.log(发送成功, message.messageUId); }, onError(error) { console.log(发送失败, error); } }); }接收消息则通过消息监听器处理RongIMClient.getInstance().setOnReceiveMessageListener({ onReceived(message) { // message.conversationType 判断是单聊还是群聊 // message.senderUserId 发送人id // message.content 消息内容 // 这里把消息push进vuex或页面响应式数据里 handleNewMessage(message); } });这里有一个实际项目中很容易踩的坑群里有人说话时所有群成员都会收到这条消息的广播但发送者自己也会收到。你需要判断message.senderUserId ! currentUserId再去更新UI否则自己发的消息会被追加两次。我的做法是在handler里统一做去重判断以messageUId为唯一标识。另外消息落库的问题也提前说一下。融云SDK虽然能拉取历史消息但客户端本地存储能力有限尤其是小程序平台建议把聊天记录的业务数据同步到你自己的服务端用消息messageUId去重避免前端重复渲染。3.4 会话列表与未读消息聊天App不可能只有聊天页会话列表页也得有。融云SDK提供了会话列表和未读数的查询接口直接遍历会话列表时可以把每个用户的头像、名称、最后一条消息展示出来。这里要提醒的是首次接入时并没有历史会话数据所以会话列表通常是空的需要先发起至少一条消息会话才会出现。测试时不要怀疑SDK坏了先用两个账号互发消息会话列表就出来了。未读数获取的方式是RongIMClient.getInstance().getUnreadCount()这个接口支持按会话类型和会话id查询也可以查所有会话的未读总数。我做红点点亮逻辑时直接监听onReceived消息事件来一条加一条点击进入聊天页清空对应会话的未读数。这样比轮询未读数接口省不少资源。4. 单人和多人音视频通话的实现细节4.1 音视频模块的引入与初始化融云IM和融云音视频RTC是两套SDKIM解决消息问题RTC解决音视频传输问题。uniapp端引入RTC能力时我用的方式是安装融云官方提供的“融云音视频通话”插件本质上是native层的封装底层是iOS和Android的融云RTC SDK。引入之后音视频模块需要和IM连接绑定调用初始化代码启动RTC服务import { RongRTC } from /uni_modules/rongcloud-rtc; function initRTC(userId) { RongRTC.init({ appKey: 你的AppKey, userId: userId, token: currentToken // 与IM连接使用的同一个token }); }这里的token直接复用IM的长连接token所以要保证是先connectIM成功后再initRTC。如果顺序反了可能出现RTC初始化时找不到IM连接状态的问题。4.2 单人音视频通话发起与接听单人通话的发起逻辑我用了一个很直观的思路先通过IM通道通知对方“我要打音视频过来了”RTC模块同时去创建通话房间。这样对端能实时收到一个自定义消息CUSTOM类型在消息拦截器里识别到之后弹出接听界面。发起端核心逻辑function callSingle(userId, mediaType) { // mediaType: audio 或 video // 1. 构造一个自定义通话信令消息通过融云IM发送给对方 // 2. 创建RTC通话实例 RongRTC.startCall({ targetId: userId, mediaType: mediaType, // 处理对方拒绝、对方无响应等回调 onHangUp: (reason) { console.log(通话结束, reason); } }); }接听端则比较简单对方发送的自定义消息到达时会触发onReceived监听在消息内容里判断是“通话邀请”然后根据mediaType拉起本地接听页面用户点击接听后调用RongRTC.acceptCall()。这里要补充一个独家经验app进程在后台或锁屏状态下自定义消息的到达可能不及时。如果做正式项目建议在onReceived里判断App是否处于前台如果不在前台走融云的推送通道Android厂商推送/iOS APNs把“来电”推出去等用户点击推送回到前台后再通过IM消息里的conversationType和targetId拉起通话界面。这个细节不做真机测试时很容易出现“你给对方打电话对方手机没反应”的问题。4.3 多人音视频通话创建房间与邀请加入多人通话跟单人通话的区别在于它有一个“房间”的概念。融云RTC的多人通话叫RongRTCRoom可以指定一个roomId然后邀请一个或多个用户加入。我的做法是发起人创建roomId把roomId通过IM发送一条CUSTOM消息给参与人参与人收到消息后加入这个roomId。核心代码// 创建并加入一个多人通话房间发起人 async function startMultiCall(roomId, userIds, mediaType) { const room await RongRTC.createRoom({ roomId: roomId, mediaType: mediaType }); // 向每个被邀请人发送IM消息带上roomId userIds.forEach(uid { sendCustomMessage(uid, invite_call, { roomId: roomId }); }); // 加入房间后开始推流把自己摄像头/麦克风数据传到房间 room.publish(); } // 被邀请人收到邀请后加入房间 async function joinMultiCall(roomId) { const room await RongRTC.joinRoom({ roomId: roomId }); room.publish(); }publish操作是把自己本地的音视频流推送到房间。多人通话最需要注意的是要管理好“谁在说话”“谁开启了摄像头”的UI状态。融云SDK会有用户加入、离开、发布流的回调我在页面上用一个Map存用户的流状态回调里增删或标记变更否则多人画面会越飘越乱。另外一个常见问题是多人通话时的噪声和回声。建议在进房间前统一设置音频自动增益和降噪参数融云RTC默认有降噪能力但不同手机原声效果差别挺大真机测试时如果对方听到回声先把音量调到中等再观察通常不是SDK问题而是测试环境靠近扬声器导致的物理回声。4.4 音视频真机调试前的权限检查清单音视频功能在模拟器上是没法完整验证的必须真机。每次换新手机调试前我都会过一遍权限清单Android 6.0以上需要动态申请权限融云SDK会自动弹出权限申请但前提是manifest里已经声明了CAMERA、RECORD_AUDIO等权限。iOS需要在manifest的App权限配置里写清NSCameraUsageDescription、NSMicrophoneUsageDescription描述文案越详细越好审核时也会看。微信小程序里无法直接走原生RTC融云的音视频小程序端方案是独立的和App方案不同。产品需求如果同时覆盖小程序和App音视频部分要提前做好技术选型评估。5. 常见问题与排查技巧实录5.1 token相关坑位连接失败、token失效、403token是整个项目里最容易出问题的地方我把遇到的典型问题列成了一张速查表现象可能原因排查方式onTokenIncorrect回调服务端AppSecret不对或客户端传的token与用户不匹配用后端日志确认token签发时用的AppKey和AppSecret再和融云开发者后台的核对连接成功后又立刻掉线同一userId被其他端重复获取token导致后获取的token顶掉前面的检查后端是否每次请求都重新生成token必要时加缓存获取token接口报403后端调用融云服务端API时的签名错误多因时间戳不统一或AppSecret复制带了空格检查服务器时间是否准确密钥配置是否多了空格消息收发正常但音视频初始化失败RTC初始化的token与IM未同一会话确认initRTC时传的token是否与connect时的最新token一致其中token被顶掉的问题尤其隐蔽。我之前测试时发现用户A在手机上正常收发消息但登录后台管理系统时把用户A的信息拉取了一遍后端代码直接调用了融云的refreshUser接口结果手机端立刻断线。融云服务端有个规则用户信息一旦刷新旧token立即失效。所以遇到“好好的突然断了”的情况优先检查后端有没有无意间调用了刷新用户信息的接口。另外有一种报错是token exchange failed相关的出现在第三方登录或推送服务对接的时候跟融云本身无关。这种情况一般是你的推送服务或认证服务那边token过期和IM token不是一回事排查时认准融云回调里的onTokenIncorrect才是IMtoken真正失效的信号。5.2 uniapp运行到微信开发者工具没反应这个问题困扰了我一晚上最后发现是manifest里没有重新获取微信小程序AppID配置或者微信开发者工具的“服务端口”没开。UNI小程序跟普通原生小程序不同它需要微信开发者工具开启“设置-安全设置-服务端口”选项HBuilder X才能推送过去。另外还有一点微信开发者工具里要选择“不使用缓存”否则改的代码推送过去还是旧包。5.3 软键盘遮挡聊天输入框聊天页在微信小程序里打开时手机自带软键盘会把输入框挡住这几乎是uniapp聊天场景标配问题。我采用的方案是监听页面onKeyboardHeightChange拿到键盘高度后把输入框bottom值动态垫高onKeyboardHeightChange(e) { this.keyboardHeight e.height; // 聊天容器高度跟着调整 this.scrollIntoViewBottom(); }注意这个API在App端和微信小程序端的行为有区别App端要设置adjustPosition: true小程序端则依赖keyboard-height-change事件或input组件的adjust-position属性。两者不能一套逻辑通吃建议封装成平台判断函数。5.4 自定义分享好友与全局分享方法冲突项目里有“分享给好友”的功能需要调用uniapp的uni.share或微信小程序的onShareAppMessage。但融云或第三方插件有时候会全局覆盖onShareAppMessage方法导致你的分享代码不生效。我的教训是不要直接在小程序页面里重写onShareAppMessage而是先调用uni.removeStorageSync判断当前是否是从自定义按钮触发的分享再通过uni.showShareMenu控制菜单显隐。同时在页面onLoad里优先拿到options.scene如果是单聊分享进来的自动打开对应聊天页。这个需求看着简单但要是没先检查全局方法是否被覆盖调试一天都找不到问题。5.5 上架与隐私合规的一些提醒项目的demo已经能跑通如果要走应用市场还有几个硬性要求必须提前做一是iOS必须设置隐私政策弹窗用户不同意就直接退出App二是Android的targetSdkVersion如果较高必须配合动态权限申请流程。uniapp的manifest里以“隐私弹窗”或“隐私政策”为关键词进行配置并在App.vue里处理用户拒绝逻辑。这一点在上架审核时是必查项不要等审核被拒了再改。写在最后这个uniapp融云IM的项目从搭建到跑通大概花了三周真正写业务代码的时间其实不多大部分时间都花在理清“token由谁签、何时失效、客户端怎么拿”这三个问题上。demo我已经整理出来包含后端token接口、uniapp前端聊天和音视频全套代码后端maven环境配置也写在文档里照着配一遍就能跑起来。最后再分享一个实用小技巧你可以在服务端给融云token配置一个过期时间策略比如每7天强制刷新一次用户信息这样客户端token自动失效后App的onTokenIncorrect回调里会自动重新请求token并重连。我在线上就是这么做的能保证异常情况下自动恢复不用用户手动杀进程重进。这个机制加上融云SDK自带的重连逻辑双保险之下对话稳定性会高很多也不容易丢失离线消息。本文还有配套的精品资源点击获取
返回列表