Java后台集成融云即时通讯IM
由于公司需求,需要使用融云开发一个客服和用户的单聊模式的聊天功能,看了一天的文档才摸清楚接入流程,这里记录一下用Java后台系统集成融云即时通讯IM的流程,以防以后用到忘记了。
不管是Web、Android、iOS、小程序,哪个端需要做出聊天功能,如果是一套完成的即时通讯项目,都需要后台服务端的支持,我的项目就是Java搭建的后台,直接上流程吧!!
很多同学私信我要源码,其实按照文章的步骤集成完成可以成功,如果需要源码的直接去下载吧demo_rongyun.rar
一:在融云注册一个账号,创建IM的应用,这是前提,只有创建了应用才有App Key、App Secret。
1.访问
https://developer.rongcloud.cn/signup
使用手机号注册账户。
2. 填写企业名称和邮箱地址。
3. 进行邮箱验证。
4. 在开发者后台点击 服务管理
。
5. 选择对应环境的 AppKey
。
二:有了appKey,appSecret之后,可以撸代码了。此步骤是搭建后台,提供获取token接口给各前端。
1.后台我的框架就是一个springboot项目,请自行创建项目,pom.xml里面引入融云的依赖包
<dependency>
<groupId>cn.rongcloud.im</groupId>
<artifactId>server-sdk-java</artifactId>
<version>3.1.6</version>
</dependency>
2.创建一个UserService类,主要作用是获取token,
生成用户在融云的唯一身份标识 Token,客户端在使用融云通讯能力前必须获取 Token,融云 SDK
每次连接服务器时,都需要向融云服务器提供 Token,以便验证身份。
意思就是各端通过SDK调用各种方法功能的时候,都需要token,才能调成功,所以我们后台需要写一个获取token的接口,供各端使用!(至于为什么各端不自己去获取token,这个问题,自己可以百度咨询,安全问题哈所以放在后台获取),
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import io.rong.RongCloud;
import io.rong.methods.user.User;
import io.rong.models.Result;
import io.rong.models.response.TokenResult;
import io.rong.models.response.UserResult;
import io.rong.models.user.UserModel;
@Service
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
private static String appKey = "你刚才注册的appKey";
private static String appSecret = "你刚才注册的appSecret";
/**
* 自定义api地址
*/
@SuppressWarnings("unused")
private static String api = "http://api-cn.ronghub.com";
/**
* API 文档: http://www.rongcloud.cn/docs/server_sdk_api/user/user.html#register
* 注册用户,生成用户在融云的唯一身份标识 Token
* @param userModel id、name、portrait三个参数必传
* @return Result
*/
public TokenResult getToken(UserModel userModel) throws Exception {
RongCloud rongCloud;
if(null == rongCloudIMProperties) {
rongCloud = RongCloud.getInstance(appKey, appSecret);
}else {
rongCloud = RongCloud.getInstance(rongCloudIMProperties.getAppKey(), rongCloudIMProperties.getAppSecret());
}
// 自定义 api 地址方式
// RongCloud rongCloud = RongCloud.getInstance(appKey, appSecret,api);
// 使用 百度 HTTPDNS 获取最快的 IP 地址进行连接
// BaiduHttpDNSUtil.setHostTypeIp("account_id", "secret", rongCloud.getApiHostType());
// 设置连接超时时间
// rongCloud.getApiHostType().setConnectTimeout(10000);
// 设置读取超时时间
// rongCloud.getApiHostType().setReadTimeout(10000);
// 获取备用域名List
// List<HostType> hosttypes = rongCloud.getApiHostListBackUp();
// 设置连接、读取超时时间
// for (HostType hosttype : hosttypes) {
// hosttype.setConnectTimeout(10000);
// hosttype.setReadTimeout(10000);
// }
User user = rongCloud.user;
TokenResult result = user.register(userModel);
logger.info("getToken: " + result.toString());
return result;
}
}
/**
* 融云IM配置映射类
*/
@Component
@ConfigurationProperties(prefix = "rongcloud.im")
public class RongCloudIMProperties {
private String appKey;
private String appSecret;
public String getAppKey() {
return appKey;
}
public void setAppKey(String appKey) {
this.appKey = appKey;
}
public String getAppSecret() {
return appSecret;
}
public void setAppSecret(String appSecret) {
this.appSecret = appSecret;
}
@Override
public String toString() {
return "RongCloudIMProperties{" +
"appKey:'" + appKey + '\'' +
", appSecret:" + appSecret +
'}';
}
}
application.yml配置
rongcloud:
im:
appKey: ***********
appSecret: *********
3.创建一个UserController类,供各端接口调用
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.rong.models.response.TokenResult;
import io.rong.models.user.UserModel;
@RestController
@RequestMapping("/v1/api/user")
public class UserController extends BaseController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserService userService;
/**
* 根据userId,userName获取token
* @param userId
* @param userName
* @return
*/
@GetMapping("/getToken")
public String getToken(String userId,String userName) {
logger.info("enter into getToken function......");
logger.info("======userId:{}=======userName:{}", userId, userName);
if(StringUtils.isEmpty(userId) || StringUtils.isEmpty(userName)) {
return responseJson("99","userId、userName请求参数都不能为空",null);
}
UserModel userModel = new UserModel();
userModel.setId(userId);
userModel.setName(userName);
userModel.setPortrait("http://www.rongcloud.cn/images/logo.png");
TokenResult result;
try {
result = userService.getToken(userModel);
} catch (Exception e) {
logger.error("获取 Token 失败 ", e);
return responseJson("999","获取 Token 失败",null);
}
return responseJson("00","请求成功",result);
}
public String responseJson(String code,String msg,Object obj){
Map<String, Object> res = new HashMap<>();
res.put("code", code);
res.put("msg", msg);
if (obj == null) {
obj = new Object();
}
res.put("data", obj);
String ret = JSONObject.toJSONString(res,SerializerFeature.WriteMapNullValue,SerializerFeature.DisableCircularReferenceDetect,SerializerFeature.PrettyFormat,SerializerFeature.SortField);
logger.info("RESPONSE:" + ret);
return ret;
}
}
三: 前端开发,其实融云提供了各个端的demo代码,我这边主要是记录接入流程,简便为主,哈哈,用的H5+JQuery手工做了一个demo。
由于不是专业前端,所以demo没有样式可言,只是功能。我这边做了2个页面,模拟了一个客服聊天页面,一个用户聊天页面。
客服端页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="format-detection" content="telephone=no"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<title>客服坐席端</title>
<script src="https://apps.bdimg.com/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="http://cdn.ronghub.com/RongIMLib-3.0.5-dev.js"></script>
<script type="text/javascript">
var connectflag = false;
$(document).ready(function(){
var im = RongIMLib.init({
appkey: '你刚才注册的appKey'
});
var conversationList = []; // 当前已存在的会话列表
im.watch({
conversation: function(event){
var updatedConversationList = event.updatedConversationList; // 更新的会话列表
console.log('更新会话汇总:', updatedConversationList);
console.log('最新会话列表:', im.Conversation.merge({
conversationList,
updatedConversationList
}));
},
message: function(event){
var message = event.message;
console.log('收到新消息:', message);
$('#jsmsg').val(message.content.content);
},
status: function(event){
var status = event.status;
console.log('连接状态码:', status);
}
});
/* 开发者后台获取或 Server API */
var user = {
token: ''
};
$.ajax({
url: "http://localhost:8080/v1/api/user/getToken",
data:{"userId":'kyy_kf_1',"userName":'客服坐席1号'},
type: "GET",
error:function (data) {
console.log(data);
},
success:function (data) {
console.log(data);
var code = data.code;
if('00' == code){
user.token = data.data.token;
}
}
});
im.connect(user).then(function(user) {
console.log('链接成功, 链接用户 id 为: ', user.id);
connectflag = true;
}).catch(function(error) {
console.log('链接失败: ', error.code, error.msg);
});
$('#send').click(function() {
if(connectflag){
var conversation = im.Conversation.get({
targetId: 'kyy_yh_1',// 用户的suerID
type: RongIMLib.CONVERSATION_TYPE.PRIVATE
});
conversation.send({
messageType: RongIMLib.MESSAGE_TYPE.TEXT, // 'RC:TxtMsg'
content: {
content: $('#fsmsg').val() // 文本内容
}
}).then(function(message){
console.log('发送文字消息成功', message);
});
}
});
});
</script>
</head>
<body>
<h2>客服坐席聊天</h2>
输入聊天消息:<input id="fsmsg" type="text" />
<input id="send" type="button" value="发送"/>
<br/>
接收到的消息:<input id="jsmsg" type="text" />
</body>
</html>
用户端页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="format-detection" content="telephone=no"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<title>用户端</title>
<script src="https://apps.bdimg.com/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="http://cdn.ronghub.com/RongIMLib-3.0.5-dev.js"></script>
<script type="text/javascript">
var connectflag = false;
$(document).ready(function(){
var im = RongIMLib.init({
appkey: '你刚才注册的appKey'
});
var conversationList = []; // 当前已存在的会话列表
im.watch({
conversation: function(event){
var updatedConversationList = event.updatedConversationList; // 更新的会话列表
console.log('更新会话汇总:', updatedConversationList);
console.log('最新会话列表:', im.Conversation.merge({
conversationList,
updatedConversationList
}));
},
message: function(event){
var message = event.message;
console.log('收到新消息:', message);
$('#jsmsg').val(message.content.content);
},
status: function(event){
var status = event.status;
console.log('连接状态码:', status);
}
});
/* 开发者后台获取或 Server API */
var user = {
token: ''
};
$.ajax({
url: "http://localhost:8080/v1/api/user/getToken",
data:{"userId":'kyy_yh_1',"userName":'用户1号'},
type: "GET",
error:function (data) {
console.log(data);
},
success:function (data) {
console.log(data);
var code = data.code;
if('00' == code){
user.token = data.data.token;
}
}
});
im.connect(user).then(function(user) {
console.log('链接成功, 链接用户 id 为: ', user.id);
connectflag = true;
}).catch(function(error) {
console.log('链接失败: ', error.code, error.msg);
});
$('#send').click(function() {
if(connectflag){
var conversation = im.Conversation.get({
targetId: 'kyy_kf_1',// 客服坐席的userID
type: RongIMLib.CONVERSATION_TYPE.PRIVATE
});
conversation.send({
messageType: RongIMLib.MESSAGE_TYPE.TEXT, // 'RC:TxtMsg'
content: {
content: $('#fsmsg').val() // 文本内容
}
}).then(function(message){
console.log('发送文字消息成功', message);
});
}
});
});
</script>
</head>
<body>
<h2>用户端聊天</h2>
输入聊天消息:<input id="fsmsg" type="text" />
<input id="send" type="button" value="发送"/>
<br/>
接收到的消息:<input id="jsmsg" type="text" />
</body>
</html>
四:页面开发好后,可以收发消息了,这些消息的数据,按理来说应该入库。
1.此时就需要再融云的控制台配置消息回调了。
我这里只配置了一个文本消息的回调,一切都是为了实现集成流程,真实项目里肯定是根据业务场景个性化配置。
2.后台创建IMCallBackApiController类,添加回调接口
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.json.JSONObject;
@Controller
@RequestMapping("/v1/api/im")
public class IMCallBackApiController {
private static final Logger logger = LoggerFactory.getLogger(IMCallBackApiController.class);
/**
* 接收即时通讯IM消息回调
* @param request
* @param appKey 应用 App Key
* @param fromUserId 发送用户 Id。
* @param targetId 目标会话 Id,根据会话类型可能为单聊 Id、群聊 Id、聊天室 Id。
* @param msgType 消息类型,文本消息 RC:TxtMsg 、 图片消息 RC:ImgMsg 其他消息类型请参见消息类型说明文档。
* @param content 发送消息内容
* @param channelType 会话类型,二人会话是 PERSON 、讨论组会话是 PERSONS 、群组会话是 GROUP 、聊天室会话是 TEMPGROUP 。
* @param msgTimeStamp 服务端收到客户端发送消息时的服务器时间(1970年到现在的毫秒数)。
* @param messageId 消息唯一标识。
* @return JSONObject { "pass": 1 } 1 表示正常下发此条消息,0 表示拒绝不下发此条消息。
*/
@PostMapping("/callBackEvent")
@ResponseBody
public JSONObject callBackEvent(HttpServletRequest request,String appKey,String fromUserId,String targetId,
String msgType,String content,String channelType,String msgTimeStamp,String messageId) {
logger.info("callBackEvent function startTime:{}", System.currentTimeMillis());
logger.info("----------收到IM 给到的回调消息appKey={}, fromUserId={},targetId={},msgType={},content={},channelType={},msgTimeStamp={},messageId={}-----------",
appKey, fromUserId, targetId, msgType, content, channelType, msgTimeStamp, messageId);
JSONObject ret = new JSONObject() {
{put("pass", 1);}
};
try {
HandleCallBackMsg handleCallBackMsg = HandleCallBackMsg
.builder()
.appKey(appKey)
.fromUserId(fromUserId)
.targetId(targetId)
.msgType(msgType)
.content(content)
.channelType(channelType)
.msgTimeStamp(msgTimeStamp)
.messageId(messageId)
.build();
//TO-DO handleCallBackMsg 实体类里就是回调的所有数据,此时就是根据自己的项目 串行或者异步把消息入库或者别的业务逻辑
} catch (Exception e) {
logger.error(e.getMessage(), e);
ret.put("pass", 0);
}
logger.info("callBackEvent function endTime:{}", System.currentTimeMillis());
return ret;
}
}
3.HandleCallBackMsg实体类
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Tolerate;
@Data
@Builder
public class HandleCallBackMsg {
/*
* 应用 App Key
*/
private String appKey;
/*
* 发送用户 Id。
*/
private String fromUserId;
/*
* 目标会话 Id,根据会话类型可能为单聊 Id、群聊 Id、聊天室 Id。
*/
private String targetId;
/*
* 消息类型,文本消息 RC:TxtMsg 、 图片消息 RC:ImgMsg 其他消息类型请参见消息类型说明文档。
*/
private String msgType;
/*
* 发送消息内容
*/
private String content;
/*
* 会话类型,二人会话是 PERSON 、讨论组会话是 PERSONS 、群组会话是 GROUP 、聊天室会话是 TEMPGROUP 。
*/
private String channelType;
/*
* 服务端收到客户端发送消息时的服务器时间(1970年到现在的毫秒数)。
*/
private String msgTimeStamp;
/*
* 消息唯一标识。
*/
private String messageId;
@Tolerate
public HandleCallBackMsg() {
}
}
总结:
以上就完成了最基础的Web+后台集成融云IM单聊收发消息的功能,聊天的消息是各端直接调用SDK和融云交互,然后融云又通过异步的消息回调把消息推送给我们的后台系统,这样就实现了即时通讯的解耦,体现了用第三方插件完成实时通讯的业务需求。
看下效果吧