//说明----很重要(引用下图三个Java类,代码可以复制)
引入外部jar包(git下载源码就有)
在spring boot启动类同一包下新建ApplicationRunnerImpl(因为监听优先于been,所已要创建,不然放到tomcat下会出问题)
package com.haoyaogroup.erp;
import com.haoyaogroup.erp.listener.ServerLauncherImpl;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
* @Author: pangruirui
* @Date: 2020/12/9 9:31
* 该类用于在springboot启动之后加载
*/
@Component
public class ApplicationRunnerImpl implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("通过实现ApplicationRunner接口");
final ServerLauncherImpl sli = new ServerLauncherImpl();
// 启动MobileIMSDK服务端的Demo
sli.startup();
// 加一个钩子,确保在JVM退出时释放netty的资源
Runtime.getRuntime().addShutdownHook(new Thread(sli::shutdown));
}
}
//pom文件引入
<!-- 引入外部jar包
${basedir}表示项目根目录,即包含pom.xml文件的目录;
-->
<dependency>
<groupId>com.xiaming</groupId>
<artifactId>gson</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${pom.basedir}/src/main/resources/lib/gson-2.8.6.jar</systemPath>
</dependency>
<dependency>
<groupId>com.xiaming</groupId>
<artifactId>MobileIMSDKServer</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${pom.basedir}/src/main/resources/lib/MobileIMSDKServer.jar</systemPath>
</dependency>
<dependency>
<groupId>com.xiaming</groupId>
<artifactId>netty</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${pom.basedir}/src/main/resources/lib/netty-all-4.1.50.Final.jar</systemPath>
</dependency>
<dependency>
<groupId>com.xiaming</groupId>
<artifactId>rabbitmq</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${pom.basedir}/src/main/resources/lib/rabbitmq-client.jar</systemPath>
</dependency>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<webResources>
<resource>
//这个很重要,不然找不到外部引入的jar包
<directory>${project.basedir}/src/main/resources/lib</directory>
<targetPath>WEB-INF/lib</targetPath>
<includes>
<include>**/*.jar</include>
</includes>
</resource>
</webResources>
</configuration>
</plugin>
//1.MessageQoSEventS2CListnerImpl
/*
* Copyright (C) 2020 即时通讯网(52im.net) & Jack Jiang.
* The MobileIMSDK v5.x Project.
* All rights reserved.
*
* > Github地址:https://github.com/JackJiang2011/MobileIMSDK
* > 文档地址: http://www.52im.net/forum-89-1.html
* > 技术社区: http://www.52im.net/
* > 技术交流群:320837163 (http://www.52im.net/topic-qqgroup.html)
* > 作者公众号:“即时通讯技术圈】”,欢迎关注!
* > 联系作者: http://www.52im.net/thread-2792-1-1.html
*
* "即时通讯网(52im.net) - 即时通讯开发者社区!" 推荐开源工程。
*
* MessageQoSEventS2CListnerImpl.java at 2020-8-22 16:00:42, code by Jack Jiang.
*/
package com.haoyaogroup.erp.listener;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.haoyaogroup.erp.model.ImAllMessage;
import com.haoyaogroup.erp.util.RedisUtil;
import net.x52im.mobileimsdk.server.event.MessageQoSEventListenerS2C;
import net.x52im.mobileimsdk.server.protocal.Protocal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.SerializationUtils;
import java.util.ArrayList;
import java.util.List;
/**
* MobileIMSDK的服务端QoS消息送达保证机制的事件监听器实现类。
* <p>
* <b>当前QoS机制支持全部的C2C、C2S、S2C共3种消息交互场景下的消息送达质量保证:<</b>>
* <ur>
* <li>1) Client to Server(C2S):即由某客户端主动发起,消息最终接收者是服务端,此模式下:重发由C保证、ACK应答由S发回;</li>
* <li>2) Server to Client(S2C):即由服务端主动发起,消息最终接收者是某客户端,此模式下:重发由S保证、ACK应答由C发回;</li>
* <li>2) Client to Client(C2C):即由客户端主动发起,消息最终接收者是另一客户端。此模式对于QoS机制来说,相当于C2S+S2C两程路径。</li>
* </ul>
* <p>
* TCP理论上能从底层保证数据的可靠性,但应用层的代码和场景中存在网络本身和网络之外的各种不可靠性,
* MobileIMSDK中的QoS送达保证机制,将加强TCP的可靠性,确保消息,无法从哪一个层面和维度,都会给
* 开发者提供两种结果:要么明确被送达(即收到ACK应答包,见 {@link #messagesBeReceived(String)})
* 、要行明确未被送达(见 {@link #messagesLost(ArrayList)})。从理论上,保证消息的百分百送达率。
*
* @author Jack Jiang
* @version 1.0
* @since 3.1
* @see net.x52im.mobileimsdk.server.qos.QoS4SendDaemonS2C
* @see net.x52im.mobileimsdk.server.qos.QoS4ReciveDaemonC2S
* @see MessageQoSEventListenerS2C
*/
public class MessageQoSEventS2CListnerImpl implements MessageQoSEventListenerS2C
{
private static Logger logger = LoggerFactory.getLogger(MessageQoSEventS2CListnerImpl.class);
/**
* 消息未送达的回调事件通知.
*
* @param lostMessages 由MobileIMSDK QoS算法判定出来的未送达消息列表(此列表
* 中的Protocal对象是原对象的clone(即原对象的深拷贝),请放心使用哦),应用层
* 可通过指纹特征码找到原消息并可以UI上将其标记为”发送失败“以便即时告之用户
*/
@Override
public void messagesLost(ArrayList<Protocal> lostMessages)
{
if(!CollectionUtils.isEmpty(lostMessages)){
for (Protocal protocal:lostMessages){
ImAllMessage imAllMessage=JSONObject.parseObject(protocal.getDataContent(),ImAllMessage.class);
List<ImAllMessage> dataList=new ArrayList<>();
dataList.add(imAllMessage);
//1,先去redis中查看是否存在此用户的信息
List<ImAllMessage> oldList =new ArrayList<>();
//判断key
byte[] in = RedisUtil.getByte("im"+protocal.getTo());
if(in !=null&&in.length>0){
oldList = (List<ImAllMessage>) SerializationUtils.deserialize(in);
}
if(CollectionUtils.isEmpty(oldList)){//没有数据直接往redis添加
RedisUtil.set("im"+protocal.getTo(),dataList);
}else {
//存在就直接往原先的list中添加
oldList.add(imAllMessage);
RedisUtil.del("im"+protocal.getTo());
//重新添加
RedisUtil.set("im"+protocal.getTo(),oldList);
}
}
}
logger.debug("【DEBUG_QoS_S2C事件】收到系统的未实时送达事件通知,当前共有"
+lostMessages.size()+"个包QoS保证机制结束,判定为【无法实时送达】!");
}
/**
* 消息已被对方收到的回调事件通知.
* <p>
* <b>目前,判定消息被对方收到是有两种可能:</b><br>
* 1) 对方确实是在线并且实时收到了;<br>
* 2) 对方不在线或者服务端转发过程中出错了,由服务端进行离线存储成功后的反馈
* (此种情况严格来讲不能算是“已被收到”,但对于应用层来说,离线存储了的消息
* 原则上就是已送达了的消息:因为用户下次登陆时肯定能通过HTTP协议取到)。
*
* @param theFingerPrint 已被收到的消息的指纹特征码(唯一ID),应用层可据此ID
* 来找到原先已发生的消息并可在UI是将其标记为”已送达“或”已读“以便提升用户体验
*/
@Override
public void messagesBeReceived(String theFingerPrint)
{
if(theFingerPrint != null)
{
logger.debug("【DEBUG_QoS_S2C事件】收到对方已收到消息事件的通知,fp="+theFingerPrint);
}
}
}
//2,ServerEventListenerImpl
/*
* Copyright (C) 2020 即时通讯网(52im.net) & Jack Jiang.
* The MobileIMSDK v5.x Project.
* All rights reserved.
*
* > Github地址:https://github.com/JackJiang2011/MobileIMSDK
* > 文档地址: http://www.52im.net/forum-89-1.html
* > 技术社区: http://www.52im.net/
* > 技术交流群:320837163 (http://www.52im.net/topic-qqgroup.html)
* > 作者公众号:“即时通讯技术圈】”,欢迎关注!
* > 联系作者: http://www.52im.net/thread-2792-1-1.html
*
* "即时通讯网(52im.net) - 即时通讯开发者社区!" 推荐开源工程。
*
* ServerEventListenerImpl.java at 2020-8-22 16:00:42, code by Jack Jiang.
*/
package com.haoyaogroup.erp.listener;
import com.alibaba.fastjson.JSONArray;
import com.haoyaogroup.erp.model.ImAllMessage;
import com.haoyaogroup.erp.util.RedisUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import lombok.SneakyThrows;
import net.x52im.mobileimsdk.server.event.ServerEventListener;
import net.x52im.mobileimsdk.server.network.Gateway;
import net.x52im.mobileimsdk.server.network.MBObserver;
import net.x52im.mobileimsdk.server.protocal.Protocal;
import net.x52im.mobileimsdk.server.qos.QoS4SendDaemonS2C;
import net.x52im.mobileimsdk.server.utils.ServerToolKits;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.SerializationUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* 与客服端的所有数据交互事件在此ServerEventListener子类中实现即可。
*
* @author Jack Jiang
* @version 1.0
* @since 3.1
*/
@Service
public class ServerEventListenerImpl implements ServerEventListener
{
private static Logger logger = LoggerFactory.getLogger(ServerEventListenerImpl.class);
public static Map map=new HashMap();
/**
* 用户身份验证回调方法定义(即验证客户端连接的合法性,合法就允许正常能信,否则断开).
* <p>
* 服务端的应用层可在本方法中实现用户登陆验证。
* <br>
* 注意:本回调在一种特殊情况下——即用户实际未退出登陆但再次发起来登陆包时,本回调是不会被调用的!
* <p>
* 根据MobileIMSDK的算法实现,本方法中用户验证通过(即方法返回值=0时)后
* ,将立即调用回调方法 {@link #}。
* 否则会将验证结果(本方法返回值错误码通过客户端的 ChatBaseEvent.onLoginResponse(int userId, int errorCode)
* 方法进行回调)通知客户端)。
*
* @param userId 传递过来的准一id,保证唯一就可以通信,可能是登陆用户名、也可能是任意不重复的id等,具体意义由业务层决定
* @param token 用于身份鉴别和合法性检查的t本字段目前为保留字段oken,它可能是登陆密码,也可能是通过前置单点登陆接口拿到的token等,具体意义由业务层决定
* @param extra 额外信息字符串。,供上层应用自行放置需要的内容
* @param session 此客户端连接对应的 netty “会话”
* @return 0 表示登陆验证通过,否则可以返回用户自已定义的错误码,错误码值应为:>=1025的整数
*/
@Override
public int onUserLoginVerify(String userId, String token, String extra, Channel session)
{
//把通道信息赋值给ChannelImpl
map.put(userId,session);
logger.debug("【DEBUG_回调通知】正在调用回调方法:OnVerifyUserCallBack...(extra="+extra+")");
return 0;
}
/**
* 用户登录验证成功后的回调方法定义(在业务上可理解为该用户的上线通知).
* <p>
* 服务端的应用层通常可在本方法中实现用户上线通知等。
* <br>
* 注意:本回调在一种特殊情况下——即用户实际未退出登陆但再次发起来登陆包时,回调也是一定会被调用。
*
* @param userId 传递过来的准一id,保证唯一就可以通信,可能是登陆用户名、也可能是任意不重复的id等,具体意义由业务层决定
* @param extra 额外信息字符串。本字段目前为保留字段,供上层应用自行放置需要的内容。为了丰富应用层处理的手段,在本回调中也把此字段传进来了
* @param session 此客户端连接对应的 netty “会话”
*/
@SneakyThrows
@Override
public void onUserLoginSucess(String userId, String extra, Channel session)
{
//把通道信息赋值给ChannelImpl
map.put(userId,session);
//推送离线消息
List<ImAllMessage> oldList =new ArrayList<>();
//判断key
byte[] in = RedisUtil.getByte("im"+userId);
if(in !=null&&in.length>0){
oldList = (List<ImAllMessage>) SerializationUtils.deserialize(in);
RedisUtil.del("im"+userId);
//去重
oldList=oldList.stream().collect(
Collectors.collectingAndThen(Collectors.toCollection(
() -> new TreeSet<>(Comparator.comparing(ImAllMessage ::getId))), ArrayList::new)
);
}
if(!CollectionUtils.isEmpty(oldList)){//没有数据直接往redis添加
for (ImAllMessage imAllMessage:oldList){
String dataContent= JSONArray.toJSON(imAllMessage).toString();
Protocal protocal=new Protocal(2,dataContent,imAllMessage.getFromUserId(),imAllMessage.getToUserId(),true,null);
sendData(session,protocal,null,imAllMessage.getConversationId(),imAllMessage);
}
}
logger.debug("【IM_回调通知OnUserLoginAction_CallBack】用户:"+userId+" 上线了!");
}
/**
* 用户退出登录回调方法定义(可理解为下线通知回调)。
* <p>
* 服务端的应用层通常可在本方法中实现用户下线通知等。
*
* @param userId 下线的用户user_id
* @param obj
* @param session 此客户端连接对应的 netty “会话”
*/
@Override
public void onUserLogout(String userId, Object obj, Channel session)
{
map.put(userId,null);
//保存客户信息到redis中
logger.debug("【DEBUG_回调通知OnUserLogoutAction_CallBack】用户:"+userId+" 离线了!");
}
/**
* 收到客户端发送给“服务端”的数据回调通知(即:消息路径为“C2S”的消息).
* <p>
* MobileIMSDK在收到客户端向userId="0"(即接收目标是"服务器")的情况下通过
* 本方法的回调通知上层。
* <p>
* <b>本方法的典型用途</b>:开发者通常可在本方法中实现如:添加好友请求等需要服务端进行处理的业务。
*
* @param p 消息/指令的完整协议包对象
* @param session 此客户端连接对应的 netty “会话”
* @return true表示本方法已成功处理完成,否则表示未处理成功。此返回值目前框架中并没有特殊意义,仅作保留吧
* @see Protocal
* @since 4.0
*/
@Override
public boolean onTransferMessage4C2S(Protocal p, Channel session)
{
// 接收者uid
String userId = p.getTo();
// 发送者uid
String from_user_id = p.getFrom();
// 消息或指令内容
String dataContent = p.getDataContent();
// 消息或指令指纹码(即唯一ID)
String fingerPrint = p.getFp();
// 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令)
int typeu = p.getTypeu();
logger.debug("【DEBUG_回调通知】[typeu="+typeu+"]收到了客户端"+from_user_id+"发给服务端的消息:str="+dataContent);
return true;
}
/**
* 收到客户端发送给“其它客户端”的数据回调通知(即:消息路径为“C2C”的消息).
* <p>
* <b>注意:</b>本方法当且仅当在数据被服务端成功实时发送(“实时”即意味着对方在线的情况下)出去后被回调调用.
* <p>
* <b>本方法的典型用途</b>:开发者可在本方法中可以实现用户聊天信息的收集,以便后期监控分析用户的行为等^_^。
* 开发者可以对本方法不作任何代码实现,也不会影响整个MobileIMSDK的运行,因为本回调并非关键逻辑,只是个普通消息传输结果的回调而已。
* <p>
* 提示:如果开启消息QoS保证,因重传机制,本回调中的消息理论上有重复的可能,请以参数 #fingerPrint
* 作为消息的唯一标识ID进行去重处理。
*
* @param p 消息/指令的完整协议包对象
* @see Protocal
* @since 4.0
*/
@Override
public void onTransferMessage4C2C(Protocal p)
{
// 接收者uid
String userId = p.getTo();
// 发送者uid
String from_user_id = p.getFrom();
// 消息或指令内容
String dataContent = p.getDataContent();
// 消息或指令指纹码(即唯一ID)
String fingerPrint = p.getFp();
// 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令)
int typeu = p.getTypeu();
logger.debug("【DEBUG_回调通知】[typeu="+typeu+"]收到了客户端"+from_user_id+"发给客户端"+userId+"的消息:str="+dataContent);
}
/**
* 服务端在进行消息发送时,当对方在线但实时发送失败、以及其它各种问题导致消息并没能正常发出时
* ,将无条件走本回调通知。
*
* <p>
* <b>注意:</b>本方法当且仅当在数据被服务端<u>在线发送</u>失败后被回调调用.
*
* <p>
* <b>举个例子:以下是一段典型的服务端消息/指令发送代码:</b>
* <pre style="border: 1px solid #eaeaea;background-color: #fff6ea;border-radius: 6px;">
* // 消息接收者的id(这个id由你自已定义,对于MobileIMSDK来说只要保证唯一性即可)
* String destinationUserId = "400069";
*
* // 这是要发送的消息("你好"是消息内容、“0”是消息发送者)
* final Protocal p = ProtocalFactory.createCommonData("你好", "0", destinationUserId, true, null, -1);
*
* // 对方在线的情况下,才需要实时发送,否则走离线处理逻辑
* if(OnlineProcessor.isOnline(destinationUserId)) {
* // netty是异步通知数据发送结果的
* MBObserver<Object> resultObserver = new MBObserver<Object>(){
* public void update(boolean sucess, Object extraObj) {
* if(sucess){
* // 你的消息/指令实时发送成功,不需要额外处理了
* }
* else{
* //【1】TODO: 你的消息/指令实时发送失败,在这里实现离线消息处理逻辑!
* }
* }
* };
*
* //【2】开始实时消息/指令的发送
* LocalSendHelper.sendData(p, resultObserver);
* }
* else{
* //【3】TODO: 你的离线消息处理逻辑!
* }
* <br>
* <font color="#0000ff">如上代码所示:“【1】【3】”代码处,开发者可以自行明确地进行离线逻辑处理,“【2】”处如
* 果实时发送时出现任何问题,将会走本回调方法进行通知,框架正是通过此回调进一步确保消息可靠性保证的。</font>
* </pre>
* <p>
*
* <p>
* <b>本方法的典型用途</b>:<br>
* 开发者可在本方法中实现离线消息的持久化存储(反正进到本回调通知的消息,就是应该被离线存储起来的)。
*
* <p>
* <b>此方法存的意义何在?</b><br>
* 发生此种情况的场景可能是:对方确实不在线(那么此方法里就可以作为离线消息处理了)、或者在发送时判断对方是在线的
* 但服务端在发送时却没有成功(这种情况就可能是通信错误或对方非正常通出但尚未到达会话超时时限)。<br><u>应用层在
* 此方法里实现离线消息的处理即可!</u>
*
* @param p 消息/指令的完整协议包对象
* @return true表示应用层已经处理了离线消息(如果该消息有QoS机制,则服务端将代为发送一条伪应答包
* (伪应答仅意味着不是接收方的实时应答,而只是存储到离线DB中,但在发送方看来也算是被对方收到,只是延
* 迟收到而已(离线消息嘛))),否则表示应用层没有处理(如果此消息有QoS机制,则发送方在QoS重传机制超时
* 后报出消息发送失败的提示)
* @see Protocal
* @see #onTransferMessage4C2C(Protocal)
* @since 4.0
*/
@Override
public boolean onTransferMessage_RealTimeSendFaild(Protocal p)
{
// 接收者uid
String userId = p.getTo();
// 发送者uid
String from_user_id = p.getFrom();
// 消息或指令内容
String dataContent = p.getDataContent();
// 消息或指令指纹码(即唯一ID)
String fingerPrint = p.getFp();
// 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令)
int typeu = p.getTypeu();
logger.debug("【DEBUG_回调通知】[typeu="+typeu+"]客户端"+from_user_id+"发给客户端"+userId+"的消息:str="+dataContent
+",因实时发送没有成功,需要上层应用作离线处理哦,否则此消息将被丢弃.");
//把离线的消息存储到redis中
return false;
}
//重新包装,使之能上传文件,小视频,语音等
/**服务端给客户端发消息 * @author xiaming * @date 2020-011-25 * **/ @RequestMapping(value = "/sendMessage",method = RequestMethod.POST) public JSONObject sendMessage(@RequestParam("image") MultipartFile[] image,@RequestParam("files") MultipartFile[] files, HttpServletRequest request) throws Exception { //转换json对象 ImModel imModel= JSON.parseObject(request.getParameter("imModel"), ImModel.class); JSONObject jsonObject = new JSONObject(); Channel fromChannel= (Channel) ServerEventListenerImpl.map.get(imModel.getFromUserId()); Channel channel= (Channel) ServerEventListenerImpl.map.get(imModel.getToUserId()); if(fromChannel==null){ jsonObject.put(Constant.RETURN_STA, -2); jsonObject.put(Constant.RETURN_MSG, "您不在线,无法发送消息,请尝试重新连接"); jsonObject.put("data",null); }else { //保存文件,图片,语音文件 //真正的访问路经 String truePath=""; String fileName=""; String TrName=""; String fileSize=""; String pictureThumUrl = null; int thumWidth = 0; int thumHight=0; if(files.length>0){ for (MultipartFile multipartFile : files) { String filePath = Constant.basePath + "im"; String oldName = multipartFile.getOriginalFilename(); TrName= oldName; fileName=ImageUtil.saveFile(multipartFile, oldName, filePath); String path = "file/im/"+ fileName; fileSize= FileSizeUtil.getReadableFileSize(multipartFile.getSize()); //正式专用 truePath=Constant.yuMing+path; //truePath="http://172.16.11.221:8080/"+path; String truePaths=Constant.basePath+"im/"+fileName; String thumName=UUID.randomUUID()+".jpg"; String genePath = Constant.basePath+"im/"+thumName; //如果是图片,要压缩,返回给app if(imModel.getType()==3){//图片 Thumbnails.of(truePaths) .scale(0.25f) //按比例放大缩小 >0 cale(scaleWidth,scaleHeight) // .size(820, 547) //设置生成图片的长宽 与scale 不能同时使用 .outputFormat("jpg") //保存为文件的格式设置 .allowOverwrite(true) //是否允许覆盖已存在的文件 .outputQuality(0.25f) //输出的图片质量 0~1 之间,否则报错 .toFile(genePath); //MiniPic.createThumbnail(truePaths,600, 400, 30,genePath ); //测试专用 //pictureThumUrl="http://172.16.11.221:8080/"+"file/im/"+thumName; //正式专用 pictureThumUrl=Constant.yuMing+"file/im/"+thumName; File picture=new File(genePath); FileInputStream fileInputStream=new FileInputStream(picture); BufferedImage sourceImg=ImageIO.read(fileInputStream); thumWidth=sourceImg.getWidth(); thumHight=sourceImg.getHeight(); fileInputStream.close(); } } } //视频第一帧 String videoImgUrl=""; if(image.length>0){ for (MultipartFile multipartFile : image) { String filePath=Constant.basePath+"im" ; String oldName=multipartFile.getOriginalFilename(); fileName=ImageUtil.saveFile(multipartFile, oldName, filePath); String path="file/im/"+fileName; //正式 videoImgUrl=Constant.yuMing+path; //测试 //videoImgUrl="http://172.16.11.221:8080/"+path; } } //把所有信息保存到数据库 ImAllMessage imAllMessage=new ImAllMessage(); //会话id imAllMessage.setConversationId(imModel.getSessionId()); //视频第一帧 imAllMessage.setVideoImgUrl(videoImgUrl); //压缩图片 imAllMessage.setPictureThumUrl(pictureThumUrl); //压缩width imAllMessage.setThumWidth(thumWidth); //hight imAllMessage.setThumHight(thumHight); //会话类型 imAllMessage.setConversationType("1"); //消息内容 if(StringUtils.isNotBlank(truePath)){ imAllMessage.setMessageContent(truePath); }else { imAllMessage.setMessageContent(imModel.getMsg()); } //消息类型 imAllMessage.setMessageType(String.valueOf(imModel.getType())); //发送人 imAllMessage.setFromUserId(imModel.getFromUserId()); //接收人 imAllMessage.setToUserId(imModel.getToUserId()); //消息产生时间 imAllMessage.setAddTime(DateUtil.getCurrentDate()); //是否读了 imAllMessage.setIsRead("1"); //语音时长 imAllMessage.setDuration(imModel.getDuration()); //长度 imAllMessage.setImageWidth(imModel.getImageWidth()); //高度 imAllMessage.setImageHeight(imModel.getImageHeight()); //文件名称 imAllMessage.setFileName(TrName); //文件大小 imAllMessage.setFileSize(fileSize); UsersModel usersModel=usersService.selectById(Integer.parseInt(imModel.getFromUserId())); if(Objects.nonNull(usersModel)){ //头像 imAllMessage.setHeadImageUrl(usersModel.getHeadImg()); //姓名 imAllMessage.setName(usersModel.getName()); } imAllMessageService.insertIm(imAllMessage); String dataContent= JSONArray.toJSON(imAllMessage).toString(); //Protocal protocal=new Protocal(2,dataContent,imAllMessage.getFromUserId(),imAllMessage.getToUserId(),true,null); //获取通道信息 Protocal protocal=new Protocal(2,dataContent,imModel.getFromUserId(),imModel.getToUserId(),true,null); //把保存到数据库存的信息并发送消息 String sessionId=imModel.getSessionId(); sendData(channel,protocal,null,sessionId,imAllMessage); jsonObject.put(Constant.RETURN_STA, 200); jsonObject.put(Constant.RETURN_MSG, "成功"); jsonObject.put("data",dataContent); } return jsonObject; }
public void sendData(Channel session, Protocal p, MBObserver resultObserver, String sessionId, ImAllMessage imAllMessage) throws Exception {
if(session == null)//通道为空,说明对方不在线
{
//消息发送失败,包括离线等其他原因,把数据存储到redis中
List<ImAllMessage> dataList=new ArrayList<>();
dataList.add(imAllMessage);
//1,先去redis中查看是否存在此用户的信息
//List<ImAllMessage> oldList = redisTemplate.opsForList().range("im"+p.getTo(), 0, -1);
List<ImAllMessage> oldList =new ArrayList<>();
//判断key
byte[] in =RedisUtil.getByte("im"+p.getTo());
if(in !=null&&in.length>0){
oldList = (List<ImAllMessage>) SerializationUtils.deserialize(in);
RedisUtil.del("im"+p.getTo());
}
if(CollectionUtils.isEmpty(oldList)){//没有数据直接往redis添加
RedisUtil.set("im"+p.getTo(),dataList);
}else {
//存在就直接往原先的list中添加
oldList.add(imAllMessage);
//重新添加
RedisUtil.set("im"+p.getTo(),oldList);
}
logger.info("[IMCORE-{}]toSession==null >> id={}的用户尝试发给客户端{}的消息:str={}因接收方的id已不在线,此次实时发送没有继续(此消息应考虑作离线处理哦)."
, Gateway.$(session), p.getFrom(), p.getTo(), p.getDataContent());
}
else
{
if(session.isActive())
{
if(p != null)
{
final byte[] res = p.toBytes();
ByteBuf to = Unpooled.copiedBuffer(res);
ChannelFuture cf = session.writeAndFlush(to);//.sync();
cf.addListener((ChannelFutureListener) future -> {
if( future.isSuccess()) {
if(p.isQoS() && !QoS4SendDaemonS2C.getInstance().exist(p.getFp())){
QoS4SendDaemonS2C.getInstance().put(p);
}
} else {
//存储离线消息
List<ImAllMessage> templateList =new ArrayList<>();
List<ImAllMessage> dataSList=new ArrayList<>();
dataSList.add(imAllMessage);
//判断key
byte[] in =RedisUtil.getByte("im"+p.getTo());
if(in !=null&&in.length>0){
templateList = (List<ImAllMessage>) SerializationUtils.deserialize(in);
RedisUtil.del("im"+p.getTo());
}
if(CollectionUtils.isEmpty(templateList)){//没有数据直接往redis添加
RedisUtil.set("im"+p.getTo(),dataSList);
}else {
//存在就直接往原先的list中添加
templateList.add(imAllMessage);
//重新添加
RedisUtil.set("im"+p.getTo(),templateList);
}
logger.warn("[IMCORE-{}]给客户端:{}的数据->{},发送失败![{}](此消息应考虑作离线处理哦)."
, Gateway.$(session), ServerToolKits.clientInfoToString(session), p.toGsonString(), res.length);
}
if(resultObserver != null)
resultObserver.update(future.isSuccess(), null);
});
// ## Bug FIX: 20171226 by JS, 上述数据的发送结果直接通过ChannelFutureListener就能知道,
// 如果此处不return,则会走到最后的resultObserver.update(false, null);,就会
// 出现一个发送方法的结果回调先是失败(错误地走到下面去了),一个是成功(真正的listener结果)
return;
// ## Bug FIX: 20171226 by JS END
}
else
{
logger.warn("[IMCORE-{}]客户端id={}要发给客户端{}的实时消息:str={}没有继续(此消息应考虑作离线处理哦)."
, Gateway.$(session), p.getFrom(), p.getTo(), p.getDataContent());
}
}
}
if(resultObserver != null)
resultObserver.update(false, null);
}
}
//3,ServerLauncherImpl
package com.haoyaogroup.erp.listener;
import net.x52im.mobileimsdk.server.ServerLauncher;
import net.x52im.mobileimsdk.server.network.Gateway;
import net.x52im.mobileimsdk.server.network.GatewayTCP;
import net.x52im.mobileimsdk.server.network.GatewayUDP;
import net.x52im.mobileimsdk.server.qos.QoS4ReciveDaemonC2S;
import net.x52im.mobileimsdk.server.qos.QoS4SendDaemonS2C;
import java.io.IOException;
/**
* IM服务的启动主类。
* <p>
* <b>友情提示:</b>其实MobileIMSDK的服务端并非只能以main的主类方式独立启
* 动,你完全可以把它放到诸如java的Web工程里作为子模块运行,不会有任何问题!
*
* @author Jack Jiang
* @version 1.0
* @since 3.1
*/
public class ServerLauncherImpl extends ServerLauncher
{
/**
* 静态类方法:进行一些全局配置设置。
*/
static
{
//读取配置文件
//当前项目下路径
/* File file = new File("");
String filePath="";
StringBuilder result = new StringBuilder();
try {
//filePath = file.getCanonicalPath();
filePath = System.getProperty("catalina.home");
//截取
filePath=filePath+"/webapps/im.txt";
System.out.println("这是获取路径:-----"+filePath);
File files = new File(filePath);
BufferedReader br = new BufferedReader(new FileReader(files));// 构造一个BufferedReader类来读取文件
String s = null;
while ((s = br.readLine()) != null) {// 使用readLine方法,一次读一行
result.append(System.lineSeparator() + s);
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}*/
// 设置MobileIMSDK服务端的UDP网络监听端口
GatewayUDP.PORT = 7901;
// 设置MobileIMSDK服务端的TCP网络监听端口
GatewayTCP.PORT =8901;
/*System.out.println("这是读取:-----"+result.toString().trim());
GatewayTCP.PORT = Integer.parseInt(result.toString().trim());*/
// 设置MobileIMSDK服务端仅支持UDP协议
// ServerLauncher.supportedGateways = Gateway.SUPPORT_UDP;
// 设置MobileIMSDK服务端仅支持TCP协议
// ServerLauncher.supportedGateways = Gateway.SUPPORT_TCP;
// 设置MobileIMSDK服务端同时支持UDP、TCP两种协议
ServerLauncher.supportedGateways = Gateway.SUPPORT_UDP | Gateway.SUPPORT_TCP;
// 开/关Demog日志的输出
QoS4SendDaemonS2C.getInstance().setDebugable(true);
QoS4ReciveDaemonC2S.getInstance().setDebugable(true);
ServerLauncher.debug = true;
// 与客户端协商一致的心跳频率模式设置
// ServerToolKits.setSenseModeUDP(SenseModeUDP.MODE_15S);
// ServerToolKits.setSenseModeTCP(SenseModeTCP.MODE_15S);
// 关闭与Web端的消息互通桥接器(其实SDK中默认就是false)
ServerLauncher.bridgeEnabled = false;
// TODO 跨服桥接器MQ的URI(本参数只在ServerLauncher.bridgeEnabled为true时有意义)
// BridgeProcessor.IMMQ_URI = "amqp://js:19844713@192.168.0.190";
// 设置最大TCP帧内容长度(不设置则默认最大是 6 * 1024字节)
// GatewayTCP.TCP_FRAME_MAX_BODY_LENGTH = 60 * 1024;
}
/**
* 实例构造方法。
*
* @throws IOException
*/
public ServerLauncherImpl() throws Exception
{
super();
}
/**
* 初始化消息处理事件监听者.
*/
@Override
protected void initListeners()
{
// ** 设置各种回调事件处理实现类
this.setServerEventListener(new ServerEventListenerImpl());
this.setServerMessageQoSEventListener(new MessageQoSEventS2CListnerImpl());
}
public static void main(String[] args) throws Exception
{
// 实例化后记得startup哦,单独startup()的目的是让调用者可以延迟决定何时真正启动IM服务
final ServerLauncherImpl sli = new ServerLauncherImpl();
// 启动MobileIMSDK服务端的Demo
sli.startup();
// 加一个钩子,确保在JVM退出时释放netty的资源
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
sli.shutdown();
}
});
}
}
//接下来就是改造sdk,因为原先的只能文本,不能小视频,文件,语音,所已需要改造
重新定义接口
package com.haoyaogroup.erp.controller.im;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.haoyaogroup.erp.listener.ServerEventListenerImpl;
import com.haoyaogroup.erp.mapper.ConversationMapper;
import com.haoyaogroup.erp.model.Conversation;
import com.haoyaogroup.erp.model.ImAllMessage;
import com.haoyaogroup.erp.model.UsersModel;
import com.haoyaogroup.erp.model.request.ImModel;
import com.haoyaogroup.erp.model.request.ImRequest;
import com.haoyaogroup.erp.service.conversation.ConversationService;
import com.haoyaogroup.erp.service.imAllMessage.ImAllMessageService;
import com.haoyaogroup.erp.service.imMessage.ImMessageService;
import com.haoyaogroup.erp.service.users.UsersService;
import com.haoyaogroup.erp.util.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import net.coobird.thumbnailator.Thumbnails;
import net.x52im.mobileimsdk.server.network.Gateway;
import net.x52im.mobileimsdk.server.network.MBObserver;
import net.x52im.mobileimsdk.server.protocal.Protocal;
import io.netty.channel.Channel;
import net.x52im.mobileimsdk.server.qos.QoS4SendDaemonS2C;
import net.x52im.mobileimsdk.server.utils.ServerToolKits;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.util.SerializationUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author xiaming
* @program: haoyaogroup-erp
* @description:
* @date 2020/11/25 10:10
*/
@RequestMapping(value = "/im")
@RestController
public class ImSendMessageController {
private static Logger logger = LoggerFactory.getLogger(ImSendMessageController.class);
@Autowired
private ImAllMessageService imAllMessageService;
@Autowired
private ConversationService conversationService;
@Autowired
private UsersService usersService;
@Autowired
private ConversationMapper conversationMapper;
@Autowired
private ImMessageService imMessageService;
/**打开会话生成会话id
* @author xiaming
* @date 2020-11-25
* **/
@RequestMapping(value = "/buildSessionId",method = RequestMethod.POST)
public JSONObject buildSessionId(@RequestBody ImModel imModel){
//根据fromUserId和toUserId查询是否已经建立会话id
boolean isCreate=true;
String sessionId= "";
Conversation conversation=new Conversation();
Conversation conversations=new Conversation();
conversation.setConvCreateUserId(imModel.getFromUserId());
conversation.setConvPartId(imModel.getToUserId());
List<Conversation> listOne=conversationService.getList(conversation);
conversations.setConvCreateUserId(imModel.getToUserId());
conversations.setConvPartId(imModel.getFromUserId());
List<Conversation> listTwo=conversationService.getList(conversations);
if(listOne.size()>0){
sessionId=listOne.get(0).getId().toString();
}
if(listTwo.size()>0){
sessionId=listTwo.get(0).getId().toString();
}
if(StringUtils.isEmpty(sessionId)){
conversation.setConversationType("1");
conversation.setConvCreatTime(DateUtil.getCurrentDate());
conversation.setConvLastTime(DateUtil.getCurrentDate());
conversationService.insertConversation(conversation);
sessionId=conversation.getId().toString();
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("data",sessionId);
jsonObject.put(Constant.RETURN_STA, 200);
jsonObject.put(Constant.RETURN_MSG, "成功");
return jsonObject;
}
@RequestMapping(value = "/setRedis",method = RequestMethod.POST)
public JSONObject setRedis(@RequestBody ImModel imModel){
RedisUtil.put("user-"+imModel.getToUserId(),imModel.getAppType());
JSONObject jsonObject = new JSONObject();
jsonObject.put(Constant.RETURN_STA, 200);
jsonObject.put(Constant.RETURN_MSG, "成功");
return jsonObject;
}
/**服务端给客户端发消息
* @author xiaming
* @date 2020-011-25
* **/
@RequestMapping(value = "/sendMessage",method = RequestMethod.POST)
public JSONObject sendMessage(@RequestParam("image") MultipartFile[] image,@RequestParam("files") MultipartFile[] files, HttpServletRequest request) throws Exception {
//转换json对象
ImModel imModel= JSON.parseObject(request.getParameter("imModel"), ImModel.class);
JSONObject jsonObject = new JSONObject();
Channel fromChannel= (Channel) ServerEventListenerImpl.map.get(imModel.getFromUserId());
Channel channel= (Channel) ServerEventListenerImpl.map.get(imModel.getToUserId());
if(fromChannel==null){
jsonObject.put(Constant.RETURN_STA, -2);
jsonObject.put(Constant.RETURN_MSG, "您不在线,无法发送消息,请尝试重新连接");
jsonObject.put("data",null);
}else {
//保存文件,图片,语音文件
//真正的访问路经
String truePath="";
String fileName="";
String TrName="";
String fileSize="";
String pictureThumUrl = null;
int thumWidth = 0;
int thumHight=0;
if(files.length>0){
for (MultipartFile multipartFile : files) {
String filePath = Constant.basePath + "im";
String oldName = multipartFile.getOriginalFilename();
TrName= oldName;
fileName=ImageUtil.saveFile(multipartFile, oldName, filePath);
String path = "file/im/"+ fileName;
fileSize= FileSizeUtil.getReadableFileSize(multipartFile.getSize());
//正式专用
truePath=Constant.yuMing+path;
//truePath="http://172.16.11.221:8080/"+path;
String truePaths=Constant.basePath+"im/"+fileName;
String thumName=UUID.randomUUID()+".jpg";
String genePath = Constant.basePath+"im/"+thumName;
//如果是图片,要压缩,返回给app
if(imModel.getType()==3){//图片
Thumbnails.of(truePaths)
.scale(0.25f) //按比例放大缩小 >0 cale(scaleWidth,scaleHeight)
// .size(820, 547) //设置生成图片的长宽 与scale 不能同时使用
.outputFormat("jpg") //保存为文件的格式设置
.allowOverwrite(true) //是否允许覆盖已存在的文件
.outputQuality(0.25f) //输出的图片质量 0~1 之间,否则报错
.toFile(genePath);
//MiniPic.createThumbnail(truePaths,600, 400, 30,genePath );
//测试专用
//pictureThumUrl="http://172.16.11.221:8080/"+"file/im/"+thumName;
//正式专用
pictureThumUrl=Constant.yuMing+"file/im/"+thumName;
File picture=new File(genePath);
FileInputStream fileInputStream=new FileInputStream(picture);
BufferedImage sourceImg=ImageIO.read(fileInputStream);
thumWidth=sourceImg.getWidth();
thumHight=sourceImg.getHeight();
fileInputStream.close();
}
}
}
//视频第一帧
String videoImgUrl="";
if(image.length>0){
for (MultipartFile multipartFile : image) {
String filePath=Constant.basePath+"im" ;
String oldName=multipartFile.getOriginalFilename();
fileName=ImageUtil.saveFile(multipartFile, oldName, filePath);
String path="file/im/"+fileName;
//正式
videoImgUrl=Constant.yuMing+path;
//测试
//videoImgUrl="http://172.16.11.221:8080/"+path;
}
}
//把所有信息保存到数据库
ImAllMessage imAllMessage=new ImAllMessage();
//会话id
imAllMessage.setConversationId(imModel.getSessionId());
//视频第一帧
imAllMessage.setVideoImgUrl(videoImgUrl);
//压缩图片
imAllMessage.setPictureThumUrl(pictureThumUrl);
//压缩width
imAllMessage.setThumWidth(thumWidth);
//hight
imAllMessage.setThumHight(thumHight);
//会话类型
imAllMessage.setConversationType("1");
//消息内容
if(StringUtils.isNotBlank(truePath)){
imAllMessage.setMessageContent(truePath);
}else {
imAllMessage.setMessageContent(imModel.getMsg());
}
//消息类型
imAllMessage.setMessageType(String.valueOf(imModel.getType()));
//发送人
imAllMessage.setFromUserId(imModel.getFromUserId());
//接收人
imAllMessage.setToUserId(imModel.getToUserId());
//消息产生时间
imAllMessage.setAddTime(DateUtil.getCurrentDate());
//是否读了
imAllMessage.setIsRead("1");
//语音时长
imAllMessage.setDuration(imModel.getDuration());
//长度
imAllMessage.setImageWidth(imModel.getImageWidth());
//高度
imAllMessage.setImageHeight(imModel.getImageHeight());
//文件名称
imAllMessage.setFileName(TrName);
//文件大小
imAllMessage.setFileSize(fileSize);
UsersModel usersModel=usersService.selectById(Integer.parseInt(imModel.getFromUserId()));
if(Objects.nonNull(usersModel)){
//头像
imAllMessage.setHeadImageUrl(usersModel.getHeadImg());
//姓名
imAllMessage.setName(usersModel.getName());
}
imAllMessageService.insertIm(imAllMessage);
String dataContent= JSONArray.toJSON(imAllMessage).toString();
//Protocal protocal=new Protocal(2,dataContent,imAllMessage.getFromUserId(),imAllMessage.getToUserId(),true,null);
//获取通道信息
Protocal protocal=new Protocal(2,dataContent,imModel.getFromUserId(),imModel.getToUserId(),true,null);
//把保存到数据库存的信息并发送消息
String sessionId=imModel.getSessionId();
sendData(channel,protocal,null,sessionId,imAllMessage);
jsonObject.put(Constant.RETURN_STA, 200);
jsonObject.put(Constant.RETURN_MSG, "成功");
jsonObject.put("data",dataContent);
}
return jsonObject;
}
public void sendData(Channel session, Protocal p, MBObserver resultObserver, String sessionId, ImAllMessage imAllMessage) throws Exception {
if(session == null)//通道为空,说明对方不在线
{
imMessageService.pushMessage(imAllMessage);
//消息发送失败,包括离线等其他原因,把数据存储到redis中
List<ImAllMessage> dataList=new ArrayList<>();
dataList.add(imAllMessage);
//1,先去redis中查看是否存在此用户的信息
List<ImAllMessage> oldList =new ArrayList<>();
//判断key
byte[] in =RedisUtil.getByte("im"+p.getTo());
if(in !=null&&in.length>0){
oldList = (List<ImAllMessage>) SerializationUtils.deserialize(in);
RedisUtil.del("im"+p.getTo());
}
if(CollectionUtils.isEmpty(oldList)){//没有数据直接往redis添加
RedisUtil.set("im"+p.getTo(),dataList);
}else {
//存在就直接往原先的list中添加
oldList.add(imAllMessage);
//重新添加
RedisUtil.set("im"+p.getTo(),oldList);
}
//更新为最后一次会话时间
Conversation conversation=new Conversation();
conversation.setId(Integer.parseInt(sessionId));
conversation.setConvLastTime(DateUtil.getCurrentDate());
conversationMapper.updateConversation(conversation);
logger.info("[IMCORE-{}]toSession==null >> id={}的用户尝试发给客户端{}的消息:str={}因接收方的id已不在线,此次实时发送没有继续(此消息应考虑作离线处理哦)."
, Gateway.$(session), p.getFrom(), p.getTo(), p.getDataContent());
}
else
{
if(StringUtils.isNotBlank(RedisUtil.get("user-"+imAllMessage.getToUserId()))&&RedisUtil.get("user-"+imAllMessage.getToUserId()).equals("2")){
imMessageService.pushMessage(imAllMessage);
//存储离线消息
List<ImAllMessage> templateList =new ArrayList<>();
List<ImAllMessage> dataSList=new ArrayList<>();
dataSList.add(imAllMessage);
//判断key
byte[] in =RedisUtil.getByte("im"+p.getTo());
if(in !=null&&in.length>0){
templateList = (List<ImAllMessage>) SerializationUtils.deserialize(in);
RedisUtil.del("im"+p.getTo());
}
if(CollectionUtils.isEmpty(templateList)){//没有数据直接往redis添加
RedisUtil.set("im"+p.getTo(),dataSList);
}else {
//存在就直接往原先的list中添加
templateList.add(imAllMessage);
//重新添加
RedisUtil.set("im"+p.getTo(),templateList);
}
}else {
if(session.isActive())
{
if(p != null)
{
final byte[] res = p.toBytes();
ByteBuf to = Unpooled.copiedBuffer(res);
ChannelFuture cf = session.writeAndFlush(to);//.sync();
cf.addListener((ChannelFutureListener) future -> {
if( future.isSuccess()) {
if(p.isQoS() && !QoS4SendDaemonS2C.getInstance().exist(p.getFp())){
QoS4SendDaemonS2C.getInstance().put(p);
}
} else {
//存储离线消息
List<ImAllMessage> templateList =new ArrayList<>();
List<ImAllMessage> dataSList=new ArrayList<>();
dataSList.add(imAllMessage);
//判断key
byte[] in =RedisUtil.getByte("im"+p.getTo());
if(in !=null&&in.length>0){
templateList = (List<ImAllMessage>) SerializationUtils.deserialize(in);
RedisUtil.del("im"+p.getTo());
}
if(CollectionUtils.isEmpty(templateList)){//没有数据直接往redis添加
RedisUtil.set("im"+p.getTo(),dataSList);
}else {
//存在就直接往原先的list中添加
templateList.add(imAllMessage);
//重新添加
RedisUtil.set("im"+p.getTo(),templateList);
}
logger.warn("[IMCORE-{}]给客户端:{}的数据->{},发送失败![{}](此消息应考虑作离线处理哦)."
, Gateway.$(session), ServerToolKits.clientInfoToString(session), p.toGsonString(), res.length);
}
if(resultObserver != null)
resultObserver.update(future.isSuccess(), null);
});
//更新为最后一次会话时间
Conversation conversation=new Conversation();
conversation.setId(Integer.parseInt(sessionId));
conversation.setConvLastTime(DateUtil.getCurrentDate());
conversationMapper.updateConversation(conversation);
// ## Bug FIX: 20171226 by JS, 上述数据的发送结果直接通过ChannelFutureListener就能知道,
// 如果此处不return,则会走到最后的resultObserver.update(false, null);,就会
// 出现一个发送方法的结果回调先是失败(错误地走到下面去了),一个是成功(真正的listener结果)
return;
// ## Bug FIX: 20171226 by JS END
}
else
{
logger.warn("[IMCORE-{}]客户端id={}要发给客户端{}的实时消息:str={}没有继续(此消息应考虑作离线处理哦)."
, Gateway.$(session), p.getFrom(), p.getTo(), p.getDataContent());
}
}
}
}
if(resultObserver != null)
resultObserver.update(false, null);
}
@RequestMapping(value = "/getOut",method = RequestMethod.POST)
public JSONObject getOut(@RequestBody ImModel imModel){
ServerEventListenerImpl.map.put(imModel.getToUserId(),null);
JSONObject jsonObjects = new JSONObject();
jsonObjects.put(Constant.RETURN_STA, 200);
jsonObjects.put(Constant.RETURN_MSG, "成功");
return jsonObjects;
}
/**从redis中获取当前相关的数据
* @author xiaming
* @date 2020-11-26
* **/
@RequestMapping(value = "/getRedis",method = RequestMethod.POST)
public JSONObject getRedis(@RequestBody ImModel imModel){
//根据key值获取相关的数据
JSONObject jsonObjects = new JSONObject();
if(Objects.nonNull(imModel)&& StringUtils.isNotBlank(imModel.getToUserId())){
//List<ImAllMessage> redisList= (List<ImAllMessage>) redisTemplate.opsForValue().get("im"+imModel.getToUserId());
List<ImAllMessage> redisList=new ArrayList<>();
byte[] in =RedisUtil.getByte("im"+imModel.getToUserId());
if(in !=null&&in.length>0){
redisList = (List<ImAllMessage>) SerializationUtils.deserialize(in);
RedisUtil.del("im"+imModel.getToUserId());
//去重
redisList=redisList.stream().collect(
Collectors.collectingAndThen(Collectors.toCollection(
() -> new TreeSet<>(Comparator.comparing(ImAllMessage ::getId))), ArrayList::new)
);
}
if(!CollectionUtils.isEmpty(redisList)){
List<ImAllMessage> allMessageList=new ArrayList<>();
List<ImRequest> finallyList=new ArrayList<>();
//用于区分不同的会话
Map<String, Object> map= new TreeMap();
for (ImAllMessage imAllMessage:redisList){
map.put(imAllMessage.getConversationId(),imAllMessage);
imAllMessage.setDateTimeStr(String.valueOf(imAllMessage.getAddTime().getTime()));
allMessageList.add(imAllMessage);
}
//区分不同的会话,把多个集合添加到最终的集合中
for(String key:map.keySet()){
ImRequest imRequest=new ImRequest();
//sessionId会话id
imRequest.setSessionId(key);
//具体对象
imRequest.setAllMessageList(allMessageList.stream().filter(a ->a.getConversationId().equals(key)).
collect(Collectors.toList()));
//条数
imRequest.setMsgCount(String.valueOf(imRequest.getAllMessageList().size()));
//添加到最终集合中
finallyList.add(imRequest);
}
jsonObjects.put("data",finallyList);
//清除redis中相关记录
//RedisUtil.del(imModel.getToUserId());
//redisTemplate.delete("im"+imModel.getToUserId());
}else {
jsonObjects.put("data",null);
}
jsonObjects.put(Constant.RETURN_STA, 200);
jsonObjects.put(Constant.RETURN_MSG, "成功");
}else {
jsonObjects.put("data",null);
jsonObjects.put(Constant.RETURN_STA, -1);
jsonObjects.put(Constant.RETURN_MSG, "当前用户不能为空");
}
return jsonObjects;
}
/**换取新手机时,拉取记录
* @author xiaming
* @date 2020-11-30
* **/
//@RequestMapping(value = "/")
}