SpringBoot集成開(kāi)源IM框架MobileIMSDK,實(shí)現(xiàn)即時(shí)通訊IM聊天功能

一、前言
MobileIMSDK 是什么?
MobileIMSDK??是一套專(zhuān)門(mén)為移動(dòng)端開(kāi)發(fā)的開(kāi)源IM即時(shí)通訊框架,超輕量級(jí)、高度提煉,一套API優(yōu)雅支持UDP 、TCP 、WebSocket 三種協(xié)議,支持iOS、Android、H5、標(biāo)準(zhǔn)Java平臺(tái),服務(wù)端基于Netty編寫(xiě)。
工程地址是:
1)Gitee碼云地址:https://www.oschina.net/p/mobileimsdk
2)Github托管地址:https://github.com/JackJiang2011/MobileIMSDK?
本文將實(shí)現(xiàn):
1)基于springboot 集成 MobileIMSDK;
2)開(kāi)發(fā)IM服務(wù)端;
3)開(kāi)發(fā)客戶端;
4)實(shí)現(xiàn)Java客戶端與客戶端之間的通信。
* 補(bǔ)充說(shuō)明:本文所示Demo源碼,請(qǐng)從文末“本文小結(jié)”的最后鏈接中下載!
二、SpringBoot 集成 MobileIMSDK 準(zhǔn)備
2.1 MobileIMSDK下載
MobileIMSDK下載地址:
1)國(guó)外地址:MobileIMSDK的Github地址(最新版打包下載)
2)國(guó)內(nèi)地址:MobileIMSDK的碼云gitee地址(訪問(wèn)速度快!,最新版打包下載)
需要用到的lib包:
1)服務(wù)端所需jar包:?sdk_binary/Server/
2)客服端所需jar包:?sdk_binary/Client_TCP/java/
如下圖所示:

2.2 pom.xml中引入相關(guān)依賴(lài)
由于這里是maven項(xiàng)目,其中一部分jar包可通過(guò)maven倉(cāng)庫(kù)直接引入,而其余的則通過(guò)外部jar包引入方式使用即可~
如下4個(gè)需作為外部jar包在pom.xml中引入 :

?
<!-- [url=https://mvnrepository.com/artifact/com.google.code.gson/gson]https://mvnrepository.com/artifact/com.google.code.gson/gson[/url] -->
<dependency>
????<groupId>com.google.code.gson</groupId>
????<artifactId>gson</artifactId>
????<version>2.8.5</version>
</dependency>
?
<!-- MobileIMSDK所需jar包依賴(lài)[注:這里是在本地lib中引入,maven中央倉(cāng)庫(kù)中暫無(wú)此jar包],要與<includeSystemScope>true</includeSystemScope>配合使用-->
<dependency>
????<groupId>com.zhengqing</groupId>
????<artifactId>MobileIMSDK4j</artifactId>
????<scope>system</scope>
????<systemPath>${project.basedir}/src/main/resources/lib/MobileIMSDK4j.jar</systemPath>
</dependency>
<dependency>
????<groupId>com.zhengqing</groupId>
????<artifactId>MobileIMSDKServerX_meta</artifactId>
????<scope>system</scope>
????<systemPath>${project.basedir}/src/main/resources/lib/MobileIMSDKServerX_meta.jar</systemPath>
</dependency>
<dependency>
????<groupId>com.zhengqing</groupId>
????<artifactId>swing-worker-1.2(1.6-)</artifactId>
????<scope>system</scope>
????<systemPath>${project.basedir}/src/main/resources/lib/swing-worker-1.2(1.6-).jar</systemPath>
</dependency>
<dependency>
????<groupId>com.zhengqing</groupId>
????<artifactId>MobileIMSDKServerX_netty</artifactId>
????<scope>system</scope>
????<systemPath>${project.basedir}/src/main/resources/lib/MobileIMSDKServerX_netty.jar</systemPath>
</dependency>
<plugins>
????<!-- maven打包插件 -> 將整個(gè)工程打成一個(gè) fatjar -->
????<plugin>
????????<groupId>org.springframework.boot</groupId>
????????<artifactId>spring-boot-maven-plugin</artifactId>
????????<!-- 作用:項(xiàng)目打成jar,同時(shí)把本地jar包也引入進(jìn)去 -->
????????<configuration>
????????????<includeSystemScope>true</includeSystemScope>
????????</configuration>
????</plugin>
</plugins>
三、開(kāi)發(fā)服務(wù)端

?
3.1 與客服端的所有數(shù)據(jù)交互事件(實(shí)現(xiàn)ServerEventListener類(lèi))
public class ServerEventListenerImpl implements ServerEventListener {
????private static Logger logger = LoggerFactory.getLogger(ServerEventListenerImpl.class);
?
????/**
?????* 用戶身份驗(yàn)證回調(diào)方法定義.
?????* <p>
?????* 服務(wù)端的應(yīng)用層可在本方法中實(shí)現(xiàn)用戶登陸驗(yàn)證。
?????* <br>
?????* 注意:本回調(diào)在一種特殊情況下——即用戶實(shí)際未退出登陸但再次發(fā)起來(lái)登陸包時(shí),本回調(diào)是不會(huì)被調(diào)用的!
?????* <p>
?????* 根據(jù)MobileIMSDK的算法實(shí)現(xiàn),本方法中用戶驗(yàn)證通過(guò)(即方法返回值=0時(shí))后
?????* ,將立即調(diào)用回調(diào)方法 {@link #onUserLoginAction_CallBack(int, String, IoSession)}。
?????* 否則會(huì)將驗(yàn)證結(jié)果(本方法返回值錯(cuò)誤碼通過(guò)客戶端的 ChatBaseEvent.onLoginMessage(int dwUserId, int dwErrorCode)
?????* 方法進(jìn)行回調(diào))通知客戶端)。
?????*
?????* @param userId? 傳遞過(guò)來(lái)的準(zhǔn)一id,保證唯一就可以通信,可能是登陸用戶名、也可能是任意不重復(fù)的id等,具體意義由業(yè)務(wù)層決定
?????* @param token?? 用于身份鑒別和合法性檢查的token,它可能是登陸密碼,也可能是通過(guò)前置單點(diǎn)登陸接口拿到的token等,具體意義由業(yè)務(wù)層決定
?????* @param extra?? 額外信息字符串。本字段目前為保留字段,供上層應(yīng)用自行放置需要的內(nèi)容
?????* @param session 此客戶端連接對(duì)應(yīng)的 netty “會(huì)話”
?????* @return 0 表示登陸驗(yàn)證通過(guò),否則可以返回用戶自已定義的錯(cuò)誤碼,錯(cuò)誤碼值應(yīng)為:>=1025的整數(shù)
?????*/
????@Override
????public int onVerifyUserCallBack(String userId, String token, String extra, Channel session) {
????????logger.debug("【DEBUG_回調(diào)通知】正在調(diào)用回調(diào)方法:OnVerifyUserCallBack...(extra="+ extra + ")");
????????return 0;
????}
?
????/**
?????* 用戶登錄驗(yàn)證成功后的回調(diào)方法定義(可理解為上線通知回調(diào)).
?????* <p>
?????* 服務(wù)端的應(yīng)用層通??稍诒痉椒ㄖ袑?shí)現(xiàn)用戶上線通知等。
?????* <br>
?????* 注意:本回調(diào)在一種特殊情況下——即用戶實(shí)際未退出登陸但再次發(fā)起來(lái)登陸包時(shí),回調(diào)也是一定會(huì)被調(diào)用。
?????*
?????* @param userId? 傳遞過(guò)來(lái)的準(zhǔn)一id,保證唯一就可以通信,可能是登陸用戶名、也可能是任意不重復(fù)的id等,具體意義由業(yè)務(wù)層決定
?????* @param extra?? 額外信息字符串。本字段目前為保留字段,供上層應(yīng)用自行放置需要的內(nèi)容。為了豐富應(yīng)用層處理的手段,在本回調(diào)中也把此字段傳進(jìn)來(lái)了
?????* @param session 此客戶端連接對(duì)應(yīng)的 netty “會(huì)話”
?????*/
????@Override
????public void onUserLoginAction_CallBack(String userId, String extra, Channel session) {
????????logger.debug("【IM_回調(diào)通知OnUserLoginAction_CallBack】用戶:"+ userId + " 上線了!");
????}
?
????/**
?????* 用戶退出登錄回調(diào)方法定義(可理解為下線通知回調(diào))。
?????* <p>
?????* 服務(wù)端的應(yīng)用層通常可在本方法中實(shí)現(xiàn)用戶下線通知等。
?????*
?????* @param userId? 下線的用戶user_id
?????* @param obj
?????* @param session 此客戶端連接對(duì)應(yīng)的 netty “會(huì)話”
?????*/
????@Override
????public void onUserLogoutAction_CallBack(String userId, Object obj, Channel session) {
????????logger.debug("【DEBUG_回調(diào)通知OnUserLogoutAction_CallBack】用戶:"+ userId + " 離線了!");
????}
?
????/**
?????* 通用數(shù)據(jù)回調(diào)方法定義(客戶端發(fā)給服務(wù)端的(即接收user_id="0")).
?????* <p>
?????* MobileIMSDK在收到客戶端向user_id=0(即接收目標(biāo)是服務(wù)器)的情況下通過(guò)
?????* 本方法的回調(diào)通知上層。上層通??稍诒痉椒ㄖ袑?shí)現(xiàn)如:添加好友請(qǐng)求等業(yè)務(wù)實(shí)現(xiàn)。
?????*
?????* <p style="background:#fbf5ee;border-radius:4px;">
?????* <b><font color="#ff0000">【版本兼容性說(shuō)明】</font></b>本方法用于替代v3.x中的以下方法:<br>
?????* <code>public boolean onTransBuffer_CallBack(String userId, String from_user_id
?????* , String dataContent, String fingerPrint, int typeu, Channel session);
?????* </code>
?????*
?????* @param userId?????? 接收方的user_id(本方法接收的是發(fā)給服務(wù)端的消息,所以此參數(shù)的值肯定==0)
?????* @param from_user_id 發(fā)送方的user_id
?????* @param dataContent? 數(shù)據(jù)內(nèi)容(文本形式)
?????* @param session????? 此客戶端連接對(duì)應(yīng)的 netty “會(huì)話”
?????* @return true表示本方法已成功處理完成,否則表示未處理成功。此返回值目前框架中并沒(méi)有特殊意義,僅作保留吧
?????* @since 4.0
?????*/
????@Override
????public boolean onTransBuffer_C2S_CallBack(Protocal p, Channel session) {
????????// 接收者uid
????????String userId = p.getTo();
????????// 發(fā)送者uid
????????String from_user_id = p.getFrom();
????????// 消息或指令內(nèi)容
????????String dataContent = p.getDataContent();
????????// 消息或指令指紋碼(即唯一ID)
????????String fingerPrint = p.getFp();
????????// 【重要】用戶定義的消息或指令協(xié)議類(lèi)型(開(kāi)發(fā)者可據(jù)此類(lèi)型來(lái)區(qū)分具體的消息或指令)
????????inttypeu = p.getTypeu();
?
????????logger.debug("【DEBUG_回調(diào)通知】[typeu="+ typeu + "]收到了客戶端"+ from_user_id + "發(fā)給服務(wù)端的消息:str="+ dataContent);
????????returntrue;
????}
?
????/**
?????* 通道數(shù)據(jù)回調(diào)函數(shù)定義(客戶端發(fā)給客戶端的(即接收方user_id不為“0”的情況)).
?????* <p>
?????* <b>注意:</b>本方法當(dāng)且僅當(dāng)在數(shù)據(jù)被服務(wù)端成功在線發(fā)送出去后被回調(diào)調(diào)用.
?????* <p>
?????* 上層通??稍诒痉椒ㄖ袑?shí)現(xiàn)用戶聊天信息的收集,以便后期監(jiān)控分析用戶的行為等^_^。
?????* <p>
?????* 提示:如果開(kāi)啟消息QoS保證,因重傳機(jī)制,本回調(diào)中的消息理論上有重復(fù)的可能,請(qǐng)以參數(shù) #fingerPrint
?????* 作為消息的唯一標(biāo)識(shí)ID進(jìn)行去重處理。
?????*
?????* <p style="background:#fbf5ee;border-radius:4px;">
?????* <b><font color="#ff0000">【版本兼容性說(shuō)明】</font></b>本方法用于替代v3.x中的以下方法:<br>
?????* <code>public void onTransBuffer_C2C_CallBack(String userId, String from_user_id
?????* , String dataContent, String fingerPrint, int typeu);
?????*
?????* @param userId?????? 接收方的user_id(本方法接收的是客戶端發(fā)給客戶端的,所以此參數(shù)的值肯定>0)
?????* @param from_user_id 發(fā)送方的user_id
?????* @param dataContent
?????* @since 4.0
?????*/
????@Override
????public void onTransBuffer_C2C_CallBack(Protocal p) {
????????// 接收者uid
????????String userId = p.getTo();
????????// 發(fā)送者uid
????????String from_user_id = p.getFrom();
????????// 消息或指令內(nèi)容
????????String dataContent = p.getDataContent();
????????// 消息或指令指紋碼(即唯一ID)
????????String fingerPrint = p.getFp();
????????// 【重要】用戶定義的消息或指令協(xié)議類(lèi)型(開(kāi)發(fā)者可據(jù)此類(lèi)型來(lái)區(qū)分具體的消息或指令)
????????inttypeu = p.getTypeu();
?
????????logger.debug("【DEBUG_回調(diào)通知】[typeu="+ typeu + "]收到了客戶端"+ from_user_id + "發(fā)給客戶端"+ userId + "的消息:str="+ dataContent);
????}
?
????/**
?????* 通用數(shù)據(jù)實(shí)時(shí)發(fā)送失敗后的回調(diào)函數(shù)定義(客戶端發(fā)給客戶端的(即接收方user_id不為“0”的情況)).
?????* <p>
?????* 注意:本方法當(dāng)且僅當(dāng)在數(shù)據(jù)被服務(wù)端<u>在線發(fā)送</u>失敗后被回調(diào)調(diào)用.
?????* <p>
?????* <b>此方法存的意義何在?</b><br>
?????* 發(fā)生此種情況的場(chǎng)景可能是:對(duì)方確實(shí)不在線(那么此方法里就可以作為離線消息處理了)、
?????* 或者在發(fā)送時(shí)判斷對(duì)方是在線的但服務(wù)端在發(fā)送時(shí)卻沒(méi)有成功(這種情況就可能是通信錯(cuò)誤
?????* 或?qū)Ψ椒钦Mǔ龅形吹竭_(dá)會(huì)話超時(shí)時(shí)限)。<br><u>應(yīng)用層在此方法里實(shí)現(xiàn)離線消息的處理即可!</u>
?????*
?????* <p style="background:#fbf5ee;border-radius:4px;">
?????* <b><font color="#ff0000">【版本兼容性說(shuō)明】</font></b>本方法用于替代v3.x中的以下方法:<br>
?????* <code>public boolean onTransBuffer_C2C_RealTimeSendFaild_CallBack(String userId
?????* , String from_user_id, String dataContent, String fingerPrint, int typeu);
?????* </code>
?????*
?????* @param userId?????? 接收方的user_id(本方法接收的是客戶端發(fā)給客戶端的,所以此參數(shù)的值肯定>0),此id在本方法中不一定保證有意義
?????* @param from_user_id 發(fā)送方的user_id
?????* @param dataContent? 消息內(nèi)容
?????* @param fingerPrint? 該消息對(duì)應(yīng)的指紋(如果該消息有QoS保證機(jī)制的話),用于在QoS重要機(jī)制下服務(wù)端離線存儲(chǔ)時(shí)防止重復(fù)存儲(chǔ)哦
?????* @return true表示應(yīng)用層已經(jīng)處理了離線消息(如果該消息有QoS機(jī)制,則服務(wù)端將代為發(fā)送一條偽應(yīng)答包
?????* (偽應(yīng)答僅意味著不是接收方的實(shí)時(shí)應(yīng)答,而只是存儲(chǔ)到離線DB中,但在發(fā)送方看來(lái)也算是被對(duì)方收到,只是延
?????* 遲收到而已(離線消息嘛))),否則表示應(yīng)用層沒(méi)有處理(如果此消息有QoS機(jī)制,則發(fā)送方在QoS重傳機(jī)制超時(shí)
?????* 后報(bào)出消息發(fā)送失敗的提示)
?????* @see #onTransBuffer_C2C_CallBack(Protocal)
?????* @since 4.0
?????*/
????@Override
????public boolean onTransBuffer_C2C_RealTimeSendFaild_CallBack(Protocal p) {
????????// 接收者uid
????????String userId = p.getTo();
????????// 發(fā)送者uid
????????String from_user_id = p.getFrom();
????????// 消息或指令內(nèi)容
????????String dataContent = p.getDataContent();
????????// 消息或指令指紋碼(即唯一ID)
????????String fingerPrint = p.getFp();
????????// 【重要】用戶定義的消息或指令協(xié)議類(lèi)型(開(kāi)發(fā)者可據(jù)此類(lèi)型來(lái)區(qū)分具體的消息或指令)
????????inttypeu = p.getTypeu();
?
????????logger.debug("【DEBUG_回調(diào)通知】[typeu="+ typeu + "]客戶端"+ from_user_id + "發(fā)給客戶端"+ userId + "的消息:str="+ dataContent
????????????????+ ",因?qū)崟r(shí)發(fā)送沒(méi)有成功,需要上層應(yīng)用作離線處理哦,否則此消息將被丟棄.");
????????returnfalse;
????}
}
3.2 服務(wù)端主動(dòng)發(fā)起消息的QoS回調(diào)通知(實(shí)現(xiàn)MessageQoSEventListenerS2C類(lèi))
public class MessageQoSEventS2CListnerImpl implements MessageQoSEventListenerS2C {
????private static Logger logger = LoggerFactory.getLogger(MessageQoSEventS2CListnerImpl.class);
?
????@Override
????public void messagesLost(ArrayList<Protocal> lostMessages) {
????????logger.debug("【DEBUG_QoS_S2C事件】收到系統(tǒng)的未實(shí)時(shí)送達(dá)事件通知,當(dāng)前共有"
????????????????+ lostMessages.size() + "個(gè)包QoS保證機(jī)制結(jié)束,判定為【無(wú)法實(shí)時(shí)送達(dá)】!");
????}
?
????@Override
????public void messagesBeReceived(String theFingerPrint) {
????????if(theFingerPrint != null) {
????????????logger.debug("【DEBUG_QoS_S2C事件】收到對(duì)方已收到消息事件的通知,fp="+ theFingerPrint);
????????}
????}
}
3.3 服務(wù)端配置
public class ServerLauncherImpl extends ServerLauncher {
????// 靜態(tài)類(lèi)方法:進(jìn)行一些全局配置設(shè)置
????static{
????????// 設(shè)置MobileIMSDK服務(wù)端的網(wǎng)絡(luò)監(jiān)聽(tīng)端口
????????ServerLauncherImpl.PORT = 7901;
?
????????// 開(kāi)/關(guān)Demog日志的輸出
????????QoS4SendDaemonS2C.getInstance().setDebugable(true);
????????QoS4ReciveDaemonC2S.getInstance().setDebugable(true);
????????ServerLauncher.debug = true;
?
????????// TODO 與客戶端協(xié)商一致的心跳敏感模式設(shè)置
//????? ServerToolKits.setSenseMode(SenseMode.MODE_10S);
?
????????// 關(guān)閉與Web端的消息互通橋接器(其實(shí)SDK中默認(rèn)就是false)
????????ServerLauncher.bridgeEnabled = false;
????????// TODO 跨服橋接器MQ的URI(本參數(shù)只在ServerLauncher.bridgeEnabled為true時(shí)有意義)
//???? BridgeProcessor.IMMQ_URI = "amqp://js:19844713@192.168.31.190";
????}
?
????// 實(shí)例構(gòu)造方法
????public ServerLauncherImpl() throws IOException {
????????super();
????}
?
????/**
?????* 初始化消息處理事件監(jiān)聽(tīng)者.
?????*/
????@Override
????protected void initListeners() {
????????// ** 設(shè)置各種回調(diào)事件處理實(shí)現(xiàn)類(lèi)
????????this.setServerEventListener(newServerEventListenerImpl());
????????this.setServerMessageQoSEventListener(newMessageQoSEventS2CListnerImpl());
????}
}
3.4 服務(wù)端啟動(dòng)類(lèi)
溫馨小提示:這里由于小編將服務(wù)端和客戶端集成在同一個(gè)項(xiàng)目中,因此如下配置:
SpringBoot的CommandLineRunner接口主要用于實(shí)現(xiàn)在服務(wù)初始化后,去執(zhí)行一段代碼塊邏輯(run方法),這段初始化代碼在整個(gè)應(yīng)用生命周期內(nèi)只會(huì)執(zhí)行一次!
@Order(value = 1) :按照一定的順序去執(zhí)行,value值越小越先執(zhí)行
@Slf4j
@Component
@Order(value = 1)
public class ChatServerRunner implements CommandLineRunner {
????@Override
????public void run(String... strings) throws Exception {
????????log.info("================= ↓↓↓↓↓↓ 啟動(dòng)MobileIMSDK服務(wù)端 ↓↓↓↓↓↓ =================");
????????// 實(shí)例化后記得startup哦,單獨(dú)startup()的目的是讓調(diào)用者可以延遲決定何時(shí)真正啟動(dòng)IM服務(wù)
????????final ServerLauncherImpl sli = new ServerLauncherImpl();
????????// 啟動(dòng)MobileIMSDK服務(wù)端的Demo
????????sli.startup();
?
????????// 加一個(gè)鉤子,確保在JVM退出時(shí)釋放netty的資源
????????Runtime.getRuntime().addShutdownHook(newThread(sli::shutdown));
????}
}
如果服務(wù)端與客戶端不在同一個(gè)項(xiàng)目 ,服務(wù)端可直接通過(guò)如下方式啟動(dòng)即可~

?
四、開(kāi)發(fā)客戶端

?
4.1 客戶端與IM服務(wù)端連接事件
@Slf4j
public class ChatBaseEventImpl implements ChatBaseEvent {
?
????@Override
????public void onLoginMessage(int dwErrorCode) {
????????if(dwErrorCode == 0) {
????????????log.debug("IM服務(wù)器登錄/連接成功!");
????????} else{
????????????log.error("IM服務(wù)器登錄/連接失敗,錯(cuò)誤代碼:"+ dwErrorCode);
????????}
????}
?
????@Override
????public void onLinkCloseMessage(int dwErrorCode) {
????????log.error("與IM服務(wù)器的網(wǎng)絡(luò)連接出錯(cuò)關(guān)閉了,error:"+ dwErrorCode);
????}
}
4.2 接收消息事件
@Slf4j
public class ChatTransDataEventImpl implements ChatTransDataEvent {
?
????@Override
????public void onTransBuffer(String fingerPrintOfProtocal, String userid, String dataContent, inttypeu) {
????????log.debug("[typeu="+ typeu + "]收到來(lái)自用戶"+ userid + "的消息:"+ dataContent);
????}
?
????@Override
????public void onErrorResponse(int errorCode, String errorMsg) {
????????log.debug("收到服務(wù)端錯(cuò)誤消息,errorCode="+ errorCode + ", errorMsg="+ errorMsg);
????}
}
4.3 消息是否送達(dá)事件
@Slf4j
public class MessageQoSEventImpl implements MessageQoSEvent {
?
????@Override// 對(duì)方未成功接收消息的回調(diào)事件 lostMessages:存放消息內(nèi)容
????public void messagesLost(ArrayList<Protocal> lostMessages) {
????????log.debug("收到系統(tǒng)的未實(shí)時(shí)送達(dá)事件通知,當(dāng)前共有"+ lostMessages.size() + "個(gè)包QoS保證機(jī)制結(jié)束,判定為【無(wú)法實(shí)時(shí)送達(dá)】!");
????}
?
????@Override// 對(duì)方成功接收到消息的回調(diào)事件
????public void messagesBeReceived(String theFingerPrint) {
????????if(theFingerPrint != null) {
????????????log.debug("收到對(duì)方已收到消息事件的通知,fp="+ theFingerPrint);
????????}
????}
}
4.4 MobileIMSDK初始化配置
public class IMClientManager {
????private static IMClientManager instance = null;
?
????/**
?????* MobileIMSDK是否已被初始化. true表示已初化完成,否則未初始化.
?????*/
????privatebooleaninit = false;
?
????public static IMClientManager getInstance() {
????????if(instance == null) {
????????????instance = new IMClientManager();
????????}
????????return instance;
????}
?
????private IMClientManager() {
????????initMobileIMSDK();
????}
?
????public void initMobileIMSDK() {
????????if(!init) {
????????????// 設(shè)置服務(wù)器ip和服務(wù)器端口
????????????ConfigEntity.serverIP = "127.0.0.1";
????????????ConfigEntity.serverPort = 8901;
?
????????????// MobileIMSDK核心IM框架的敏感度模式設(shè)置
//?????????? ConfigEntity.setSenseMode(SenseMode.MODE_10S);
?
????????????// 開(kāi)啟/關(guān)閉DEBUG信息輸出
????????????ClientCoreSDK.DEBUG = false;
?
????????????// 設(shè)置事件回調(diào)
????????????ClientCoreSDK.getInstance().setChatBaseEvent(newChatBaseEventImpl());
????????????ClientCoreSDK.getInstance().setChatTransDataEvent(newChatTransDataEventImpl());
????????????ClientCoreSDK.getInstance().setMessageQoSEvent(newMessageQoSEventImpl());
?
????????????init = true;
????????}
????}
}
4.5 連接IM服務(wù)端,發(fā)送消息
服務(wù)類(lèi):
public interface IChatService {
?
????/**
?????* 登錄連接IM服務(wù)器請(qǐng)求
?????*
?????* @param username: 用戶名
?????* @param password: 密碼
?????* @return: void
?????*/
????void loginConnect(String username, String password);
?
????/**
?????* 發(fā)送消息
?????*
?????* @param friendId: 接收消息者id
?????* @param msg:????? 消息內(nèi)容
?????* @return: void
?????*/
????void sendMsg(String friendId, String msg);
}
服務(wù)實(shí)現(xiàn)類(lèi):
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class ChatServiceImpl implements IChatService {
????@Override
????public void loginConnect(String username, String password) {
????????// 確保MobileIMSDK被初始化哦(整個(gè)app生生命周期中只需調(diào)用一次哦)
????????// 提示:在不退出app的情況下退出登陸后再重新登陸時(shí),請(qǐng)確保調(diào)用本方法一次,不然會(huì)報(bào)code=203錯(cuò)誤哦!
????????IMClientManager.getInstance().initMobileIMSDK();
?
????????// * 異步提交登陸名和密碼
????????new LocalUDPDataSender.SendLoginDataAsync(username, password) {
????????????/**
?????????????* 登陸信息發(fā)送完成后將調(diào)用本方法(注意:此處僅是登陸信息發(fā)送完成,真正的登陸結(jié)果要在異步回調(diào)中處理哦)。
?????????????* @param code 數(shù)據(jù)發(fā)送返回碼,0 表示數(shù)據(jù)成功發(fā)出,否則是錯(cuò)誤碼
?????????????*/
????????????protected void fireAfterSendLogin(int code) {
????????????????if(code == 0) {
????????????????????log.debug("數(shù)據(jù)發(fā)送成功!");
????????????????} else{
????????????????????log.error("數(shù)據(jù)發(fā)送失敗。錯(cuò)誤碼是:"+ code);
????????????????}
????????????}
????????}.execute();
????}
?
????@Override
????public void sendMsg(String friendId, String msg) {
????????// 發(fā)送消息(異步提升體驗(yàn),你也可直接調(diào)用LocalUDPDataSender.send(..)方法發(fā)送)
????????new LocalUDPDataSender.SendCommonDataAsync(msg, friendId) {
????????????@Override
????????????protected void onPostExecute(Integer code) {
????????????????if(code == 0) {
????????????????????log.debug("數(shù)據(jù)已成功發(fā)出!");
????????????????} else{
????????????????????log.error("數(shù)據(jù)發(fā)送失敗。錯(cuò)誤碼是:"+ code + "!");
????????????????}
????????????}
????????}.execute();
????}
}
五、編寫(xiě)Controller進(jìn)行測(cè)試
@RestController
@RequestMapping("/api")
@Api(tags = "聊天測(cè)試-接口")
public class ChatController {
????@Autowired
????private IChatService chatService;
?
????@PostMapping(value = "/loginConnect", produces = Constants.CONTENT_TYPE)
????@ApiOperation(value = "登陸請(qǐng)求", httpMethod = "POST", response = ApiResult.class)
????public ApiResult loginConnect(@RequestParamString username, @RequestParamString password) {
????????chatService.loginConnect(username, password);
????????return ApiResult.ok();
????}
?
????@PostMapping(value = "/sendMsg", produces = Constants.CONTENT_TYPE)
????@ApiOperation(value = "發(fā)送消息", httpMethod = "POST", response = ApiResult.class)
????public ApiResult sendMsg(@RequestParam String friendId, @RequestParam String msg) {
????????chatService.sendMsg(friendId, msg);
????????return ApiResult.ok();
????}
}
啟動(dòng)項(xiàng)目,訪問(wèn):http://127.0.0.1:8080/swagger-ui.html

?
1) loginConnect接口:
任意輸入一個(gè)賬號(hào)密碼登錄連接IM服務(wù)端:

控制臺(tái)日志如下:

?
2)sendMsg接口:
給指定用戶發(fā)送消息:這里由于只有一個(gè)客戶端,上一步登錄了一個(gè)admin賬號(hào),因此小編給admin賬號(hào)(也就是自己) 發(fā)送消息

控制臺(tái)日志如下:

六、本文小結(jié)
關(guān)于集成可參考MobileIMSDK給出的文檔一步一步實(shí)現(xiàn)。
該開(kāi)源工程對(duì)應(yīng)的官方文檔比較齊全,需要哪個(gè)端,就去看對(duì)應(yīng)端的手冊(cè)就好了。
1)Demo安裝和使用
客戶端Demo安裝和使用幫助(Android)?[1]
客戶端Demo安裝和使用幫助(iOS)?[2]
客戶端Demo安裝和使用幫助(Java)?[3]
客戶端Demo演示和說(shuō)明(H5)?[4]
服務(wù)端Demo安裝和使用幫助?[5]?new
2)開(kāi)發(fā)者指南
客戶端開(kāi)發(fā)指南(Android)
客戶端開(kāi)發(fā)指南(iOS)
客戶端開(kāi)發(fā)指南(Java)
客戶端開(kāi)發(fā)指南(H5)
服務(wù)端開(kāi)發(fā)指南
3)API文檔
客戶端SDK API文檔(Android):TCP版、UDP版
客戶端SDK API文檔(iOS):TCP版、UDP版
客戶端SDK API文檔(Java):TCP版、UDP版
客戶端SDK API文檔(H5):點(diǎn)此進(jìn)入
服務(wù)端SDK API文檔
另外:作者給出了通過(guò)Java GUI編程實(shí)現(xiàn)的一個(gè)小demo,我們可以先將其運(yùn)行起來(lái),先體驗(yàn)一下功能,代碼量也不是太多,我們可以通過(guò)debug方式查看執(zhí)行流程。
清楚執(zhí)行流程之后我們就可以將demo中的代碼移植到我們自己的項(xiàng)目中加以修改運(yùn)用于自己的業(yè)務(wù)中,切勿拿起就跑,否則一旦運(yùn)氣不好,將浪費(fèi)更多的時(shí)間去集成,這樣很不好!


?
最后:案例demo中相關(guān)代碼注釋都有,這里就簡(jiǎn)單說(shuō)下整個(gè)流程吧:
1)首先啟動(dòng)IM服務(wù)端
2)用戶在客戶端登錄一個(gè)用戶與服務(wù)端建立連接保持通信( 客戶端ChatServiceImpl中l(wèi)oginConnect方法為登錄連接服務(wù)端事件;服務(wù)端ServerEventListenerImpl中onUserLoginVerify方法為服務(wù)端接收的上線通知事件);
3)客戶端通過(guò) ChatServiceImpl中sendMsg方法發(fā)送一條消息,如果對(duì)方在線能接收消息則走服務(wù)端ServerEventListenerImpl中onTransferMessage4C2C方法,否則走onTransferMessage_RealTimeSendFaild方法;如果對(duì)方成功接收到消息,客戶端將走M(jìn)essageQoSEventImpl中messagesBeReceived事件,否則走messagesLost事件;
4)客戶端通過(guò)ChatMessageEvent中onRecieveMessage回調(diào)事件接收消息。
附:本文案例demo源碼下載:
1)主地址:https://gitee.com/zhengqingya/java-workspace
2)備地址:https://gitee.com/instant_messaging_network/java-workspace
附錄:更多IM聊天新手實(shí)踐代碼
《跟著源碼學(xué)IM(一):手把手教你用Netty實(shí)現(xiàn)心跳機(jī)制、斷線重連機(jī)制》
《跟著源碼學(xué)IM(二):自已開(kāi)發(fā)IM很難?手把手教你擼一個(gè)Andriod版IM》
《跟著源碼學(xué)IM(三):基于Netty,從零開(kāi)發(fā)一個(gè)IM服務(wù)端》
《跟著源碼學(xué)IM(四):拿起鍵盤(pán)就是干,教你徒手開(kāi)發(fā)一套分布式IM系統(tǒng)》
《跟著源碼學(xué)IM(五):正確理解IM長(zhǎng)連接、心跳及重連機(jī)制,并動(dòng)手實(shí)現(xiàn)》
《跟著源碼學(xué)IM(六):手把手教你用Go快速搭建高性能、可擴(kuò)展的IM系統(tǒng)》
《跟著源碼學(xué)IM(七):手把手教你用WebSocket打造Web端IM聊天》
《跟著源碼學(xué)IM(八):萬(wàn)字長(zhǎng)文,手把手教你用Netty打造IM聊天》
《跟著源碼學(xué)IM(九):基于Netty實(shí)現(xiàn)一套分布式IM系統(tǒng)》
《跟著源碼學(xué)IM(十):基于Netty,搭建高性能IM集群(含技術(shù)思路+源碼)》