# Yi-IM **Repository Path**: ghycn/Yi-IM ## Basic Information - **Project Name**: Yi-IM - **Description**: 即插即拔的IM聊天插件,基于netty-socketIO实现, 支持单机,集群模式。支持动消息持久化。 - **Primary Language**: Java - **License**: GPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 5 - **Created**: 2022-07-26 - **Last Updated**: 2022-07-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 仓库地址 Gitee : https://gitee.com/cnm_1314/Yi-IM ## 功能特点 ``` 1. starter插件式,即插即拔 2. 基于netty-socketIO实现, 有良好的高并发抗压能力 3. 实现socket动态认证监听器 4. 支持单机版本(内存模式),集群版本(redis模式) 5. 消息自动存储,也可自定义重写 6. 支持web api方式通讯, 只需添加依赖im-api 7. 默认单机模式,集群模式只需添加依赖im-redis 8. 支持频道分组监听,添加@ChannelListener("group")进行注册 9. 支持注解形式消息处理器,可直接扩展 ``` ## 使用说明 ### 单机模式 #### 1. 引入starter ``` com.itic im-spring-boot-starter 1.0 im: socket: host: 127.0.0.1 port: 9901 # roomId: default # channel: default # namespace: /default # 消息发送失败时,重试次数 retry: 3 # 默认是否插入消息到数据库中, 如果自己实现ImMsgHandler,该参数没用 insert-db: true 执行: t_im_msg.sql ``` #### 2. 继承DefaultEventHandler,实现自定义事件端点 ``` @Component public class ImEventHandler extends DefaultEventHandler { /** * 消息推送 自定义事件 * * @param client * @param request * @param data */ @OnEvent("自定义") public void onEvent2(SocketIOClient client, AckRequest request, SoMap data) { } } ``` #### 3. 实现自定义消息体处理, 如果不实现则使用DefaultImMsgHandler处理 ``` /** * 图片消息处理 * date: 2022/1/17 21:16 * author: yangwl */ @ImMsgType(value = MsgType.PICTURE) @Component public class ImPictureMsgHandler implements ImMsgHandler { @Override public void handler(Object o) { // 以下可自定义 // 持久化数据库 if(null == o) { throw new ImException("msg content is null"); } SoMap data = (SoMap) o; ImMsg imMsg = new ImMsg(); imMsg.setChatType(data.getString("groupType")); imMsg.setSendUid(data.getString("fromUserId")); imMsg.setReceiveUid(data.getString("toUserId")); imMsg.setGroupId(data.getString("toGroupId")); imMsg.setMsgType(data.getString("msgType")); imMsg.setMsg(data.getString("msg")); ImManager.getImMsgRepository().saveImMsg(imMsg); } } ``` #### 4. 各种默认监听器, 不实现则用默认的 ``` /** * 认证监听 * @author wanli.yang * @version 1.0 * @date 2022/1/13 9:55 */ @Component public class ImAuthorizationListener implements AuthorizationListener { @Override public boolean isAuthorized(HandshakeData data) { // 实现自己得监听逻辑 return true; } } DefaultConnectListener 连接监听器 DefaultDisConnectListener 断开连接监听器 DefaultExceptionListener 异常监听器 DefaultPingListener Ping监听器 DefaultRegisterListener 注册监听器 ``` #### 5. 启动 ``` /** * SocketIOServer启动器 * @author wanli.yang */ @Component public class SocketServerRunner implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(SocketServerRunner.class); @Override public void run(String... args) { // 启动 ImManager.start(); logger.warn("IM实时通讯系统,启动成功."); } @PreDestroy public void stop() { ImManager.stop(); } } ``` #### 6. 前端 ``` im.js 中 // new 则开启连接 ws = new IM_SOCKET(function (clientId) { // 注册成功回调 }) // 绑定事件,要和服务端定义的一样 ws.on(IM_EVENT.SEND_MSG_EVENT, function (data) { console.log(data) }); // 调用服务端某个监听事件端点 ws.emit(IM_EVENT.REGISTER_EVENT, {clientId: me.config.clientId, ......}, function(arg1, arg2) { // 回调 }); // 客户端加入指定房间 ws.joinRoom(roomId, function(r) { // 回调 console.log(r) }) ``` ### 集群模式 #### 1. 引入starter ``` com.itic im-spring-boot-starter 1.0 com.itic im-redis 1.0 ``` #### 2. 初始化房间与频道绑定 ``` // 创建房间与频道对应关系, 根据实际业务进行绑定, 我这里根据群聊组 groupInfos.stream().forEach(e -> { if( 1 == e.getGroupType()) { RedisImManager.createChannel("room-"+e.getGroupId(), "group"); } else { RedisImManager.createChannel("room-"+e.getGroupId(), "person"); } }); ``` #### 3. 自定义频道监听器 (如果不实现则使用默认频道监听器RedisSubMessageListener) ``` /** * 自定义redis message监听器 群聊监听器 * @author wanli.yang * @version 1.0 * @date 2022/1/13 18:22 */ @Component @ChannelListener("group") public class ImMessageListener implements MessageListener { private static final Logger logger = LoggerFactory.getLogger(ImMessageListener.class); @Autowired private ImRedisTemplateUtil imRedisTemplateUtil; @Override public void onMessage(Message message, byte[] pattern) { SoMap map = JSONUtil.toBean((String) imRedisTemplateUtil.valueDeserialize(message.getBody()), SoMap.class); BroadcastOperations bo = ImManager.defaultNamespace().getRoomOperations(map.getString("roomId")); BroadcastAckCallback broadcastAckCallback = new BroadcastAckCallback(String.class) { @Override public void onClientSuccess(SocketIOClient client, String result) { // logger.info("客户端{}发送成功,返回值={}", client.getSessionId(), result); } @Override public void onClientTimeout(SocketIOClient client) { logger.info("客户端{}, 发送超时,第{}次重试...", client.getSessionId(), 1); ImManager.getImTemplate().retry(client, map, 1); } }; String excludedClientId = map.getString("excludedClientId"); if(StrUtil.isNotEmpty(excludedClientId)) { Online online = (Online) ImManager.getDao().getOnline(excludedClientId); SocketIOClient client = ImManager.defaultNamespace().getClient(online.getSessionId()); bo.sendEvent(map.getString("eventName"), map, client, broadcastAckCallback); } else { bo.sendEvent(map.getString("eventName"), map, broadcastAckCallback); } } } ``` #### 4. 其他用法和单机版本就没区别了 ``` 1. 集群模式不通之处在于加入的redis,使用redis的发布/订阅模式,服务端收到消息,向redis频道pub消息, listener监听频道实现自动获取消息 2. 如果想在广播消息后做一些后置处理,可以实现PersistenceHandler接口,实现after方法 ``` ### WEB API调用 ``` 某些情况发送消息不能使用 // 调用服务端某个监听事件端点 ws.emit(IM_EVENT.REGISTER_EVENT, {clientId: me.config.clientId, ......}, function(arg1, arg2) { // 回调 }); 那么提供web api模式调用服务端事件,引入im-api模块, 目前只有一个方法 com.itic im-api 1.0 /** * 实时通讯API * @author wanli.yang * @version 1.0 * @date 2022/1/13 13:36 */ @Api(tags = "实时通讯") @RestController @RequestMapping("/im") public class ImController { @ApiOperation("发送消息") @PostMapping("/sendMsg") public R sendMsg(SoMap map) { ImManager.getImTemplate().handler(map); return R.ok(); } } ``` ## 目前只有这些基础功能, 其他开发中............ ## 更新 2022-01-13 ``` 1. 创建im-starter 2. ImConfig配置 3. 重写 SocketIOServer 支持动态添加认证监听 4. 添加SoMap 5. 添加ImTemplate 模板方法消息通讯, 支持直连模式,redis模式 6. 添加Dao,数据持久化,支持内存模式 redis模式 7. im-api 实现endpoint 公共api 8. 动态添加频道 9. 实现redis模式,一个房间对应一个频道,提高并发能力 ``` 2022-01-14 ``` 1. 扩展DefaultEventHandler 全局事件监听 ``` 2022-01-17 ``` 1. 添加redisMessageListener注解监听 如何绑定一个频道? 1. @ChannelListener("group")声明监听类 2. RedisImManager.createChannel("room-"+e.getGroupId(), "group"); 指定要绑定的频道 3. 如果不执行2, 那么底层会用默认的监听 @ChannelListener(IMConstants.IM_DEFAULT_CHANNEL) 2. 自定义注解消息类型处理起 ``` ## QQ:[597392641] ## 技术交流QQ群:[570968411]