im-system 第九章(上)

ChatOperateReceiver

逻辑层需要接收到im tcp接入层发送过来的消息,逻辑层监听使用ChatOperateReceiver,tcp层收到来自sdk的消息后,将消息发送到mq,逻辑层接收到该消息

@Component
public class ChatOperateReceiver {

    private static Logger logger = LoggerFactory.getLogger(ChatOperateReceiver.class);

    @Autowired
    P2PMessageService p2PMessageService;

    @Autowired
    MessageSyncService messageSyncService;

    /*
        声明了1个 名为 pipeline2MessageService 的队列,
        声明了1个 名为 pipeline2MessageService 的交换机(默认是直连类型),
        (Direct类型,1个Direct类型交换机可以绑定多个消息队列,每绑定1个消息队列时,需要指定对应的路由key(routeKey),
         当Direct类型交换机收到1个消息时,会根据消息发送时所指定的路由key发送给routeKey完全匹配到的消息队列)
        显然, 这里没有指定路由key, 但是经过测试, 当发送1个消息到这个直连交换机, 并且不指定路由key时,
        会把消息路由到所有绑定给该交换机但是未指定路由key的队列, 就好像是广播一样

    */
    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(value = Constants.RabbitConstants.Im2MessageService, durable = "true"),
                    exchange = @Exchange(value = Constants.RabbitConstants.Im2MessageService, durable = "true")
            ),
            concurrency = "1"
    )
    public void onChatMessage(@Payload Message message, @Headers Map<String, Object> headers, Channel channel) throws Exception {

        // service逻辑层 接收来自 tcp接入层 发送过来的消息, 并处理消息
        String msg = new String(message.getBody(), "utf-8");

        logger.info("CHAT MSG FORM QUEUE ::: {}", msg);

        // 拿到消息投递标识
        Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);

        try {

            JSONObject jsonObject = JSON.parseObject(msg);

            // 拿到指令
            Integer command = jsonObject.getInteger("command");

            if (command.equals(MessageCommand.MSG_P2P.getCommand())) {

                // 如果是 单聊消息, 交给 p2PMessageService 处理

                //处理消息
                MessageContent messageContent = jsonObject.toJavaObject(MessageContent.class);

                p2PMessageService.process(messageContent);

            } else if (command.equals(MessageCommand.MSG_RECIVE_ACK.getCommand())) {

                //消息接收确认
                MessageReciveAckContent messageContent = jsonObject.toJavaObject(MessageReciveAckContent.class);

                messageSyncService.receiveMark(messageContent);

            } else if (command.equals(MessageCommand.MSG_READED.getCommand())) {

                //消息接收确认
                MessageReadedContent messageContent = jsonObject.toJavaObject(MessageReadedContent.class);

                messageSyncService.readMark(messageContent);

            } else if (Objects.equals(command, MessageCommand.MSG_RECALL.getCommand())) {

                // 撤回消息
                RecallMessageContent messageContent = JSON.parseObject(msg, new TypeReference<RecallMessageContent>() {}.getType());

                messageSyncService.recallMessage(messageContent);
            }

            // 确认消息, 不批量确认
            channel.basicAck(deliveryTag, false);

        } catch (Exception e) {

            logger.error("处理消息出现异常:{}", e.getMessage());

            logger.error("RMQ_CHAT_TRAN_ERROR", e);

            logger.error("NACK_MSG:{}", msg);

            //第一个false 表示不批量拒绝,第二个false表示不重回队列
            channel.basicNack(deliveryTag, false, false);
        }

    }


}

P2PMessageService

用于处理单聊消息
第一步:需要作前置校验,校验好友关系、黑名单等
第二步:A发消息给B,A首先将消息发给IM服务,IM服务需要回1个ACK给A,让A知道这个消息已经发送成功,然后IM服务将消息同步给A的所有在线端,然后再发送消息给B的所有在线端

@Service
public class P2PMessageService {

    private static Logger logger = LoggerFactory.getLogger(P2PMessageService.class);

    @Autowired
    CheckSendMessageService checkSendMessageService;

    @Autowired
    MessageProducer messageProducer;

    @Autowired
    MessageStoreService messageStoreService;

    @Autowired
    RedisSeq redisSeq;

    @Autowired
    AppConfig appConfig;

    @Autowired
    CallbackService callbackService;


    private final ThreadPoolExecutor threadPoolExecutor;

    {
        final AtomicInteger num = new AtomicInteger(0);
        threadPoolExecutor = new ThreadPoolExecutor(8, 8, 60, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(1000), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setDaemon(true);
                thread.setName("message-process-thread-" + num.getAndIncrement());
                return thread;
            }
        });
    }


    public void process(MessageContent messageContent) {

        logger.info("消息开始处理:{}", messageContent.getMessageId());
        String fromId = messageContent.getFromId();
        String toId = messageContent.getToId();
        Integer appId = messageContent.getAppId();

        MessageContent messageFromMessageIdCache = messageStoreService.getMessageFromMessageIdCache(messageContent.getAppId(), messageContent.getMessageId(), MessageContent.class);

        if (messageFromMessageIdCache != null) {
            threadPoolExecutor.execute(() -> {

                // 1. 回ack给自己(IM服务告诉客户端逻辑层已收到客户端发送过来的消息)
                ack(messageContent, ResponseVO.successResponse());

                // 2. 发消息给同步在线端
                syncToSender(messageFromMessageIdCache, messageFromMessageIdCache);

                // 3. 发消息给对方在线端
                List<ClientInfo> clientInfos = dispatchMessage(messageFromMessageIdCache);

                if (clientInfos.isEmpty()) {
                    // 发送接收确认给发送方,要带上是服务端发送的标识
                    receiverAck(messageFromMessageIdCache);
                }
            });

            return;
        }

        // 回调
        ResponseVO responseVO = ResponseVO.successResponse();
        if (appConfig.isSendMessageAfterCallback()) {
            responseVO = callbackService.beforeCallback(
                    messageContent.getAppId(),
                    Constants.CallbackCommand.SendMessageBefore,
                    JSONObject.toJSONString(messageContent)
            );
        }

        if (!responseVO.isOk()) {
            ack(messageContent, responseVO);
            return;
        }

        long seq = redisSeq.doGetSeq(messageContent.getAppId()
                + ":"
                + Constants.SeqConstants.Message
                + ":"
                + ConversationIdGenerate.generateP2PId(messageContent.getFromId(), messageContent.getToId())
        );
        messageContent.setMessageSequence(seq);

        // 前置校验(将原本写在此处的校验, 以接口的形式提供给tcp层调用, 校验不通过的不投递给逻辑层处理, 以减少对mq资源的浪费)
        // 1. 这个用户是否被禁言 是否被禁用
        // 2. 发送方和接收方是否是好友
        // ResponseVO responseVO = imServerPermissionCheck(fromId, toId, appId);
        // if(responseVO.isOk()){
        //    // 1. 回ack给自己
        //    ack(messageContent,responseVO);
        //    // 2. 发送消息同步给自己的在线端
        //    syncToSender(messageContent, messageContent);
        //    // 3. 发送消息同步给对方的在线端
        //    dispatchMessage(messageContent)
        // }else{
        //     //告诉客户端失败了
        //     //ack
        //     ack(messageContent,responseVO);
        // }
        threadPoolExecutor.execute(() -> {

            //appId + Seq + (from + to) groupId
            messageStoreService.storeP2PMessage(messageContent);

            OfflineMessageContent offlineMessageContent = new OfflineMessageContent();
            BeanUtils.copyProperties(messageContent, offlineMessageContent);
            offlineMessageContent.setConversationType(ConversationTypeEnum.P2P.getCode());
            messageStoreService.storeOfflineMessage(offlineMessageContent);

            //插入数据
            //1. 回ack成功给自己
            ack(messageContent, ResponseVO.successResponse());

            //2. 发消息给同步在线端
            syncToSender(messageContent, messageContent);

            //3. 发消息给对方在线端
            List<ClientInfo> clientInfos = dispatchMessage(messageContent);

            messageStoreService.setMessageFromMessageIdCache(
                    messageContent.getAppId(),
                    messageContent.getMessageId(),
                    messageContent
            );

            if (clientInfos.isEmpty()) {

                //发送接收确认给发送方,要带上是服务端发送的标识
                receiverAck(messageContent);
            }

            if (appConfig.isSendMessageAfterCallback()) {
                callbackService.callback(
                        messageContent.getAppId(),
                        Constants.CallbackCommand.SendMessageAfter,
                        JSONObject.toJSONString(messageContent)
                );
            }

            logger.info("消息处理完成:{}", messageContent.getMessageId());
        });
    }

    // 向 发送该消息的客户端 回ack, 确认已收到客户端发送过来的该条消息
    private void ack(MessageContent messageContent, ResponseVO responseVO) {

        logger.info("msg ack, msgId={}, checkResult{}", messageContent.getMessageId(), responseVO.getCode());

        // ack消息体
        ChatMessageAck chatMessageAck = new ChatMessageAck(messageContent.getMessageId(), messageContent.getMessageSequence());

        responseVO.setData(chatMessageAck);

        // 发消息(发送给某个用户的指定客户端)
        messageProducer.sendToUser(
                messageContent.getFromId(),
                MessageCommand.MSG_ACK,
                responseVO,
                messageContent // 继承自ClientInfo, 携带了clientType, imei号
        );
    }

    public void receiverAck(MessageContent messageContent) {
        MessageReciveServerAckPack pack = new MessageReciveServerAckPack();
        pack.setFromId(messageContent.getToId());
        pack.setToId(messageContent.getFromId());
        pack.setMessageKey(messageContent.getMessageKey());
        pack.setMessageSequence(messageContent.getMessageSequence());
        pack.setServerSend(true);
        messageProducer.sendToUser(
                messageContent.getFromId(),
                MessageCommand.MSG_RECIVE_ACK,
                pack,
                new ClientInfo(messageContent.getAppId(), messageContent.getClientType(), messageContent.getImei())
        );
    }

    // 同步消息给发送方在线端
    private void syncToSender(MessageContent messageContent, ClientInfo clientInfo) {
        messageProducer.sendToUserExceptClient(
                messageContent.getFromId(),
                MessageCommand.MSG_P2P,
                messageContent,// 消息体内容
                messageContent // 继承自ClientInfo, 携带了clientType, imei号
        );
    }

    public ResponseVO imServerPermissionCheck(String fromId, String toId, Integer appId) {

        // 检查fromId用户是否被禁用或禁言
        ResponseVO responseVO = checkSendMessageService.checkSenderForbidAndMute(fromId, appId);

        if (!responseVO.isOk()) {
            return responseVO;
        }

        // 检查好友关系
        responseVO = checkSendMessageService.checkFriendShip(fromId, toId, appId);

        return responseVO;
    }

    public SendMessageResp send(SendMessageReq req) {

        SendMessageResp sendMessageResp = new SendMessageResp();
        MessageContent message = new MessageContent();
        BeanUtils.copyProperties(req, message);
        //插入数据
        messageStoreService.storeP2PMessage(message);
        sendMessageResp.setMessageKey(message.getMessageKey());
        sendMessageResp.setMessageTime(System.currentTimeMillis());

        //2.发消息给同步在线端
        syncToSender(message, message);
        //3.发消息给对方在线端
        dispatchMessage(message);
        return sendMessageResp;
    }

    // 发消息给对方在线端
    private List<ClientInfo> dispatchMessage(MessageContent messageContent) {
        List<ClientInfo> clientInfos = messageProducer.sendToUser(
                messageContent.getToId(),
                MessageCommand.MSG_P2P,
                messageContent,
                messageContent.getAppId()
        );
        return clientInfos;
    }

}

CheckSendMessageService

用于检验是否有权限发送消息,为了能够不浪费mq的资源,把校验操作以接口的形式提供出来,给tcp层调用

@Service
public class CheckSendMessageService {

    @Autowired
    ImUserService imUserService;

    @Autowired
    ImFriendService imFriendService;

    @Autowired
    ImGroupService imGroupService;

    @Autowired
    ImGroupMemberService imGroupMemberService;

    @Autowired
    AppConfig appConfig;


    public ResponseVO checkSenderForbidAndMute(String fromId, Integer appId) {

        // 获取fromId用户信息
        ResponseVO<ImUserDataEntity> responseVO = imUserService.getSingleUserInfo(fromId, appId);
        if (!responseVO.isOk()) {
            return responseVO;
        }

        ImUserDataEntity user = responseVO.getData();

        if (user.getForbiddenFlag() == UserForbiddenFlagEnum.FORBIDDEN.getCode()) {

            // fromId用户被禁用
            return ResponseVO.errorResponse(MessageErrorCode.FROMER_IS_FORBIDDEN);

        } else if (user.getSilentFlag() == UserSilentFlagEnum.MUTE.getCode()) {

            // fromId用户被禁言
            return ResponseVO.errorResponse(MessageErrorCode.FROMER_IS_MUTE);
        }

        return ResponseVO.successResponse();
    }

    public ResponseVO checkFriendShip(String fromId, String toId, Integer appId) {

        // 发送消息是否校验关系链(此处可由用户配置, 即将此开关记录在表中)
        if (appConfig.isSendMessageCheckFriend()) {

            // A -> B 关系链
            GetRelationReq fromReq = new GetRelationReq();
            fromReq.setFromId(fromId);
            fromReq.setToId(toId);
            fromReq.setAppId(appId);
            ResponseVO<ImFriendShipEntity> fromRelation = imFriendService.getRelation(fromReq);
            if (!fromRelation.isOk()) {
                return fromRelation;
            }

            // B -> A 关系链
            GetRelationReq toReq = new GetRelationReq();
            fromReq.setFromId(toId);
            fromReq.setToId(fromId);
            fromReq.setAppId(appId);
            ResponseVO<ImFriendShipEntity> toRelation = imFriendService.getRelation(toReq);
            if (!toRelation.isOk()) {
                return toRelation;
            }

            // A -> B 关系链 (未添加 或 已删除)
            if (FriendShipStatusEnum.FRIEND_STATUS_NORMAL.getCode() != fromRelation.getData().getStatus()) {
                return ResponseVO.errorResponse(FriendShipErrorCode.FRIEND_IS_DELETED);
            }

            // B -> A 关系链 (未添加 或 已删除)
            if (FriendShipStatusEnum.FRIEND_STATUS_NORMAL.getCode() != toRelation.getData().getStatus()) {
                return ResponseVO.errorResponse(FriendShipErrorCode.FRIEND_IS_DELETED);
            }

            // 发送消息是否校验黑名单(此处可由用户配置, 即将此开关记录在表中)
            if (appConfig.isSendMessageCheckBlack()) {

                // A -> B 关系链 (拉黑)
                if (FriendShipStatusEnum.BLACK_STATUS_NORMAL.getCode() != fromRelation.getData().getBlack()) {
                    return ResponseVO.errorResponse(FriendShipErrorCode.FRIEND_IS_BLACK);
                }

                // B -> A 关系链 (拉黑)
                if (FriendShipStatusEnum.BLACK_STATUS_NORMAL.getCode() != toRelation.getData().getBlack()) {
                    return ResponseVO.errorResponse(FriendShipErrorCode.TARGET_IS_BLACK_YOU);
                }
            }
        }

        return ResponseVO.successResponse();
    }

    public ResponseVO checkGroupMessage(String fromId, String groupId, Integer appId) {

        // 检查 fromId用户 是否被 禁言 或 禁用
        ResponseVO responseVO = checkSenderForbidAndMute(fromId, appId);
        if (!responseVO.isOk()) {
            return responseVO;
        }

        // 判断群是否存在逻辑
        ResponseVO<ImGroupEntity> group = imGroupService.getGroup(groupId, appId);
        if (!group.isOk()) {
            return group;
        }

        // 判断群成员是否在群内
        ResponseVO<GetRoleInGroupResp> roleInGroupOne = imGroupMemberService.getRoleInGroupOne(groupId, fromId, appId);
        if (!roleInGroupOne.isOk()) {
            return roleInGroupOne;
        }
        GetRoleInGroupResp data = roleInGroupOne.getData();

        // 判断群是否被禁言
        // 如果禁言 只有群管理和群主可以发言
        ImGroupEntity groupData = group.getData();
        if (groupData.getMute() == GroupMuteTypeEnum.MUTE.getCode()
                && data.getRole() != GroupMemberRoleEnum.MANAGER.getCode()
                && data.getRole() != GroupMemberRoleEnum.OWNER.getCode()
        ) {
            return ResponseVO.errorResponse(GroupErrorCode.THIS_GROUP_IS_MUTE);
        }

        // 群成员禁言截止时间
        if (data.getSpeakDate() != null && data.getSpeakDate() > System.currentTimeMillis()) {
            return ResponseVO.errorResponse(GroupErrorCode.GROUP_MEMBER_IS_SPEAK);
        }

        return ResponseVO.successResponse();
    }


}

MqMessageProducer

客户端使用sdk连接上tcp接入层,然后将数据包发过来经过MessageDecoder解码转为Message对象,然后到NettyServerHandler#channelRead方法,在这个收到消息的方法中,根据消息command指令作不同处理

@Slf4j
public class MqMessageProducer {

    public static void sendMessage(Message message, Integer command) {

        Channel channel = null;

        String com = command.toString();

        // 获取指令 首字符, 指令类型
        String commandSub = com.substring(0, 1);
        CommandType commandType = CommandType.getCommandType(commandSub);

        String channelName = "";

        // 根据指令类型, 选择不同的交换机
        if (commandType == CommandType.MESSAGE) {

            // channelName为 pipeline2MessageService
            channelName = Constants.RabbitConstants.Im2MessageService;

        } else if (commandType == CommandType.GROUP) {

            // channelName为 pipeline2GroupService
            channelName = Constants.RabbitConstants.Im2GroupService;

        } else if (commandType == CommandType.FRIEND) {

            // channelName为 pipeline2FriendshipService
            channelName = Constants.RabbitConstants.Im2FriendshipService;

        } else if (commandType == CommandType.USER) {

            // channelName为 pipeline2UserService
            channelName = Constants.RabbitConstants.Im2UserService;
        }

        try {

            // 拿到对应channelName的 Channel 对象
            //(针对每个交换机 都有 1个 Channel对象 与之对应)
            channel = MqFactory.getChannel(channelName);

            JSONObject o = (JSONObject) JSON.toJSON(message.getMessagePack());
            o.put("command", command);
            o.put("clientType", message.getMessageHeader().getClientType());
            o.put("imei", message.getMessageHeader().getImei());
            o.put("appId", message.getMessageHeader().getAppId());

            // 用与交换机对应的channel对象 发送到名为 channelName 的交换机, 路由key为空字符串, 不指定属性, 消息内容
            //(这也就是说: tcp发送到rabbitmq中的上面4个交换机, 都使用针对每个交换机而创建的Channel对象,
            //            而逻辑层则使用了@RabbitListener注解来为这些个交换机绑定了各自的队列, 并消费消息)
            channel.basicPublish(channelName, "", null, o.toJSONString().getBytes());

        } catch (Exception e) {

            log.error("发送消息出现异常:{}", e.getMessage());
        }
    }

    public static void sendMessage(Object message, MessageHeader header, Integer command) {

        Channel channel = null;

        String com = command.toString();

        // 获取指令 首字符, 指令类型
        String commandSub = com.substring(0, 1);
        CommandType commandType = CommandType.getCommandType(commandSub);

        String channelName = "";

        if (commandType == CommandType.MESSAGE) {

            // channelName为 pipeline2MessageService
            channelName = Constants.RabbitConstants.Im2MessageService;

        } else if (commandType == CommandType.GROUP) {

            // channelName为 pipeline2GroupService
            channelName = Constants.RabbitConstants.Im2GroupService;

        } else if (commandType == CommandType.FRIEND) {

            // channelName为 pipeline2FriendshipService
            channelName = Constants.RabbitConstants.Im2FriendshipService;

        } else if (commandType == CommandType.USER) {

            // channelName为 pipeline2UserService
            channelName = Constants.RabbitConstants.Im2UserService;
        }

        try {

            // 拿到对应channelName的 Channel 对象
            channel = MqFactory.getChannel(channelName);

            JSONObject o = (JSONObject) JSON.toJSON(message);
            o.put("command", command);
            o.put("clientType", header.getClientType());
            o.put("imei", header.getImei());
            o.put("appId", header.getAppId());

            // 发送到名为 channelName 的交换机, 路由key为空字符串, 不指定属性, 消息内容
            //(这也就是说: tcp发送到rabbitmq中的上面4个交换机, 都使用各自创建的Channel对象,
            //            而逻辑层则使用了@RabbitListener注解来为这些个交换机绑定了各自的队列, 并消费消息)
            channel.basicPublish(channelName, "", null, o.toJSONString().getBytes());

        } catch (Exception e) {
            log.error("发送消息出现异常:{}", e.getMessage());
        }
    }

}

web.html

启动服务端,开2个web.html,分别登录用户A和用户B,A发送消息给B,A收到了汇报消息,B收到了A发送过来的消息

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket客户端</title>
</head>
<body>
<script type="text/javascript">


    ByteBuffer = function (arrayBuf, offset) {

        var Type_Byte = 1;
        var Type_Short = 2;
        var Type_UShort = 3;
        var Type_Int32 = 4;
        var Type_UInt32 = 5;
        var Type_String = 6;//变长字符串,前两个字节表示长度
        var Type_VString = 7;//定长字符串
        var Type_Int64 = 8;
        var Type_Float = 9;
        var Type_Double = 10;
        var Type_ByteArray = 11;

        var _org_buf = arrayBuf ? (arrayBuf.constructor == DataView ? arrayBuf : (arrayBuf.constructor == Uint8Array ? new DataView(arrayBuf.buffer, offset) : new DataView(arrayBuf, offset))) : new DataView(new Uint8Array([]).buffer);
        var _offset = offset || 0;
        var _list = [];
        var _littleEndian = false;

        //指定字节序 为BigEndian
        this.bigEndian = function () {
            _littleEndian = false;
            return this;
        };

        //指定字节序 为LittleEndian
        this.littleEndian = function () {
            _littleEndian = true;
            return this;
        };

        if (!ArrayBuffer.prototype.slice) {
            ArrayBuffer.prototype.slice = function (start, end) {
                var that = new Uint8Array(this);
                if (end == undefined) end = that.length;
                var result = new ArrayBuffer(end - start);
                var resultArray = new Uint8Array(result);
                for (var i = 0; i < resultArray.length; i++)
                    resultArray[i] = that[i + start];
                return result;
            }
        }

        function utf8Write(view, offset, str) {
            var c = 0;
            for (var i = 0, l = str.length; i < l; i++) {
                c = str.charCodeAt(i);
                if (c < 0x80) {
                    view.setUint8(offset++, c);
                } else if (c < 0x800) {
                    view.setUint8(offset++, 0xc0 | (c >> 6));
                    view.setUint8(offset++, 0x80 | (c & 0x3f));
                } else if (c < 0xd800 || c >= 0xe000) {
                    view.setUint8(offset++, 0xe0 | (c >> 12));
                    view.setUint8(offset++, 0x80 | (c >> 6) & 0x3f);
                    view.setUint8(offset++, 0x80 | (c & 0x3f));
                } else {
                    i++;
                    c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
                    view.setUint8(offset++, 0xf0 | (c >> 18));
                    view.setUint8(offset++, 0x80 | (c >> 12) & 0x3f);
                    view.setUint8(offset++, 0x80 | (c >> 6) & 0x3f);
                    view.setUint8(offset++, 0x80 | (c & 0x3f));
                }
            }
        }

        function utf8Read(view, offset, length) {
            var string = '', chr = 0;
            for (var i = offset, end = offset + length; i < end; i++) {
                var byte = view.getUint8(i);
                if ((byte & 0x80) === 0x00) {
                    string += String.fromCharCode(byte);
                    continue;
                }
                if ((byte & 0xe0) === 0xc0) {
                    string += String.fromCharCode(
                        ((byte & 0x0f) << 6) |
                        (view.getUint8(++i) & 0x3f)
                    );
                    continue;
                }
                if ((byte & 0xf0) === 0xe0) {
                    string += String.fromCharCode(
                        ((byte & 0x0f) << 12) |
                        ((view.getUint8(++i) & 0x3f) << 6) |
                        ((view.getUint8(++i) & 0x3f) << 0)
                    );
                    continue;
                }
                if ((byte & 0xf8) === 0xf0) {
                    chr = ((byte & 0x07) << 18) |
                        ((view.getUint8(++i) & 0x3f) << 12) |
                        ((view.getUint8(++i) & 0x3f) << 6) |
                        ((view.getUint8(++i) & 0x3f) << 0);
                    if (chr >= 0x010000) { // surrogate pair
                        chr -= 0x010000;
                        string += String.fromCharCode((chr >>> 10) + 0xD800, (chr & 0x3FF) + 0xDC00);
                    } else {
                        string += String.fromCharCode(chr);
                    }
                    continue;
                }
                throw new Error('Invalid byte ' + byte.toString(16));
            }
            return string;
        }

        function utf8Length(str) {
            var c = 0, length = 0;
            for (var i = 0, l = str.length; i < l; i++) {
                c = str.charCodeAt(i);
                if (c < 0x80) {
                    length += 1;
                } else if (c < 0x800) {
                    length += 2;
                } else if (c < 0xd800 || c >= 0xe000) {
                    length += 3;
                } else {
                    i++;
                    length += 4;
                }
            }
            return length;
        }

        this.byte = function (val, index) {
            if (arguments.length == 0) {
                _list.push(_org_buf.getUint8(_offset, _littleEndian));
                _offset += 1;
            } else {
                _list.splice(index != undefined ? index : _list.length, 0, {t: Type_Byte, d: val, l: 1});
                _offset += 1;
            }
            return this;
        };

        this.short = function (val, index) {
            if (arguments.length == 0) {
                _list.push(_org_buf.getInt16(_offset, _littleEndian));
                _offset += 2;
            } else {
                _list.splice(index != undefined ? index : _list.length, 0, {t: Type_Short, d: val, l: 2});
                _offset += 2;
            }
            return this;
        };

        this.ushort = function (val, index) {
            if (arguments.length == 0) {
                _list.push(_org_buf.getUint16(_offset, _littleEndian));
                _offset += 2;
            } else {
                _list.splice(index != undefined ? index : _list.length, 0, {t: Type_UShort, d: val, l: 2});
                _offset += 2;
            }
            return this;
        };

        this.int32 = function (val, index) {
            if (arguments.length == 0) {
                _list.push(_org_buf.getInt32(_offset, _littleEndian));
                _offset += 4;
            } else {
                _list.splice(index != undefined ? index : _list.length, 0, {t: Type_Int32, d: val, l: 4});
                _offset += 4;
            }
            return this;
        };

        this.uint32 = function (val, index) {
            if (arguments.length == 0) {
                _list.push(_org_buf.getUint32(_offset, _littleEndian));
                _offset += 4;
            } else {
                _list.splice(index != undefined ? index : _list.length, 0, {t: Type_UInt32, d: val, l: 4});
                _offset += 4;
            }
            return this;
        };

        /**
         * 新加的方法,获取bytebuffer的长度
         */
        this.blength = function () {
            return _offset;
        };

        /**
         * 变长字符串 前4个字节表示字符串长度
         **/
        this.string = function (val, index) {
            if (arguments.length == 0) {
                var len = _org_buf.getInt32(_offset, _littleEndian);
                _offset += 4;
                _list.push(utf8Read(_org_buf, _offset, len));
                _offset += len;
            } else {
                var len = 0;
                if (val) {
                    len = utf8Length(val);
                }
                _list.splice(index != undefined ? index : _list.length, 0, {t: Type_String, d: val, l: len});
                _offset += len + 4;
            }
            return this;
        };

        /**
         * 定长字符串 val为null时,读取定长字符串(需指定长度len)
         **/
        this.vstring = function (val, len, index) {
            if (!len) {
                throw new Error('vstring must got len argument');
                return this;
            }
            if (val == undefined || val == null) {
                var vlen = 0;//实际长度
                for (var i = _offset; i < _offset + len; i++) {
                    if (_org_buf.getUint8(i) > 0) vlen++;
                }
                _list.push(utf8Read(_org_buf, _offset, vlen));
                _offset += len;
            } else {
                _list.splice(index != undefined ? index : _list.length, 0, {t: Type_VString, d: val, l: len});
                _offset += len;
            }
            return this;
        };

        this.int64 = function (val, index) {
            if (arguments.length == 0) {
                _list.push(_org_buf.getFloat64(_offset, _littleEndian));
                _offset += 8;
            } else {
                _list.splice(index != undefined ? index : _list.length, 0, {t: Type_Int64, d: val, l: 8});
                _offset += 8;
            }
            return this;
        };

        this.float = function (val, index) {
            if (arguments.length == 0) {
                _list.push(_org_buf.getFloat32(_offset, _littleEndian));
                _offset += 4;
            } else {
                _list.splice(index != undefined ? index : _list.length, 0, {t: Type_Float, d: val, l: 4});
                _offset += 4;
            }
            return this;
        };

        this.double = function (val, index) {
            if (arguments.length == 0) {
                _list.push(_org_buf.getFloat64(_offset, _littleEndian));
                _offset += 8;
            } else {
                _list.splice(index != undefined ? index : _list.length, 0, {t: Type_Double, d: val, l: 8});
                _offset += 8;
            }
            return this;
        };

        /**
         * 写入或读取一段字节数组
         **/
        this.byteArray = function (val, len, index) {
            if (!len) {
                throw new Error('byteArray must got len argument');
                return this;
            }
            if (val == undefined || val == null) {
                var arr = new Uint8Array(_org_buf.buffer.slice(_offset, _offset + len));
                _list.push(arr);
                _offset += len;
            } else {
                _list.splice(index != undefined ? index : _list.length, 0, {t: Type_ByteArray, d: val, l: len});
                _offset += len;
            }
            return this;
        };

        /**
         * 解包成数据数组
         **/
        this.unpack = function () {
            return _list;
        };

        /**
         * 打包成二进制,在前面加上4个字节表示包长
         **/
        this.packWithHead = function () {
            return this.pack(true);
        };

        /**
         * 打包成二进制
         * @param ifHead 是否在前面加上4个字节表示包长
         **/
        this.pack = function (ifHead) {
            _org_buf = new DataView(new ArrayBuffer((ifHead) ? _offset + 4 : _offset));
            var offset = 0;
            if (ifHead) {
                _org_buf.setUint32(offset, _offset, _littleEndian);
                offset += 4;
            }
            for (var i = 0; i < _list.length; i++) {
                switch (_list[i].t) {
                    case Type_Byte:
                        _org_buf.setInt8(offset, _list[i].d);
                        offset += _list[i].l;
                        break;
                    case Type_Short:
                        _org_buf.setInt16(offset, _list[i].d, _littleEndian);
                        offset += _list[i].l;
                        break;
                    case Type_UShort:
                        _org_buf.setUint16(offset, _list[i].d, _littleEndian);
                        offset += _list[i].l;
                        break;
                    case Type_Int32:
                        _org_buf.setInt32(offset, _list[i].d, _littleEndian);
                        offset += _list[i].l;
                        break;
                    case Type_UInt32:
                        _org_buf.setUint32(offset, _list[i].d, _littleEndian);
                        offset += _list[i].l;
                        break;
                    case Type_String:
                        //前4个字节表示字符串长度
                        _org_buf.setUint32(offset, _list[i].l, _littleEndian);
                        offset += 4;
                        utf8Write(_org_buf, offset, _list[i].d);
                        offset += _list[i].l;
                        break;
                    case Type_VString:
                        utf8Write(_org_buf, offset, _list[i].d);
                        var vlen = utf8Length(_list[i].d);//字符串实际长度
                        //补齐\0
                        for (var j = offset + vlen; j < offset + _list[i].l; j++) {
                            _org_buf.setUint8(j, 0);
                        }
                        offset += _list[i].l;
                        break;
                    case Type_Int64:
                        _org_buf.setFloat64(offset, _list[i].d, _littleEndian);
                        offset += _list[i].l;
                        break;
                    case Type_Float:
                        _org_buf.setFloat32(offset, _list[i].d, _littleEndian);
                        offset += _list[i].l;
                        break;
                    case Type_Double:
                        _org_buf.setFloat64(offset, _list[i].d, _littleEndian);
                        offset += _list[i].l;
                        break;
                    case Type_ByteArray:
                        var indx = 0;
                        for (var j = offset; j < offset + _list[i].l; j++) {
                            if (indx < _list[i].d.length) {
                                _org_buf.setUint8(j, _list[i].d[indx]);
                            } else {//不够的话,后面补齐0x00
                                _org_buf.setUint8(j, 0);
                            }
                            indx++
                        }
                        offset += _list[i].l;
                        break;
                }
            }
            return _org_buf.buffer;
        };

        /**
         * 未读数据长度
         **/
        this.getAvailable = function () {
            if (!_org_buf) return _offset;
            return _org_buf.buffer.byteLength - _offset;
        };
    }

    function uuid() {
        var s = [];
        var hexDigits = "0123456789abcdef";
        for (var i = 0; i < 36; i++) {
            s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
        }
        s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
        s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
        s[8] = s[13] = s[18] = s[23] = "-";

        var uuid = s.join("");
        return uuid;
    }

    var socket;

    //如果浏览器支持WebSocket
    if (window.WebSocket) {
        //参数就是与服务器连接的地址
        socket = new WebSocket("ws://localhost:19000/ws");

        //客户端收到服务器消息的时候就会执行这个回调方法
        socket.onmessage = function (event) {
            var ta = document.getElementById("responseText");
            var userId = document.getElementById("userId").value;

            var bytebuf = new ByteBuffer(event.data);
            let byteBuffer = bytebuf.int32().int32().unpack();

            let command = byteBuffer[0];
            let bodyLen = byteBuffer[1];
            let unpack = bytebuf.vstring(null, bodyLen).unpack();
            let msgBody = unpack[2];
            var version = 1;

            var clientType = document.getElementById("clientType").value;
            clientType = parseInt(clientType);
            var messageType = 0x0;
            var appId = document.getElementById("appId").value;
            appId = parseInt(appId)

            var imei = document.getElementById("imei").value;
            var imeiLen = getLen(imei);

            console.log("收到服务端发来的消息: " + msgBody);

            if(command == 1103){
                var d = JSON.parse(msgBody)
                var data = d.data;
                let userId = document.getElementById('userId').value;
                if(data.fromId == userId){
                    ta.value = ta.value + "\n" + "自己:" + data.messageBody;
                }else if(data.fromId != userId){
                    ta.value = ta.value + "\n" + data.fromId + ":" + data.messageBody;
                }

                // data.fromId = data.toId;
                // data.conversationId = "0_" + data.toId + "_" + data.fromId;
                // data.conversationType = 0;

                if(userId != data.fromId){
                    var rAck = {
                        "fromId":userId,
                        "toId":data.fromId,
                        "messageKey":data.messageKey,
                        "messageId":data.messageId,
                        "messageSequence":data.messageSequence,
                    }

                    let messageReciver = new ByteBuffer();
                    var jsonData = JSON.stringify(rAck);
                    let bodyLen = jsonData.length;
                    messageReciver.int32(1107)
                        .int32(version).int32(clientType)
                        .int32(messageType).int32(appId)
                        .int32(imeiLen).int32(bodyLen).vstring(imei,imeiLen)
                        .vstring(jsonData, bodyLen);
                    socket.send(messageReciver.pack());
                }

                if(clientType == 1 ){
                    let messageReader = new ByteBuffer();
                    var toId = data.fromId;
                    if(data.fromId == userId){
                        toId = data.toId;
                    }
                    var readedData = {
                        "fromId":userId,
                        "toId":toId,
                        "conversationType":0,
                        "messageSequence":data.messageSequence,
                    }
                    var readData = JSON.stringify(readedData);
                    let readBodyLen = readData.length;
                    messageReader.int32(1106)
                        .int32(version).int32(clientType)
                        .int32(messageType).int32(appId)
                        .int32(imeiLen).int32(readBodyLen).vstring(imei,imeiLen)
                        .vstring(readData, readBodyLen);
                    socket.send(messageReader.pack());
                }

            }else if(command == 2104){
                var d = JSON.parse(msgBody)
                var data = d.data;
                let userId = document.getElementById('userId').value;

                if(clientType == 1){
                    let messageReader = new ByteBuffer();
                    var toId = data.fromId;
                    if(data.fromId == userId){
                        toId = data.toId;
                    }
                    var readedData = {
                        "fromId":userId,
                        "toId":data.fromId,
                        "groupId":data.groupId,
                        "conversationType":1,
                        "messageSequence":data.messageSequence,
                    }
                    var readData = JSON.stringify(readedData);
                    let readBodyLen = readData.length;
                    messageReader.int32(2106)
                        .int32(version).int32(clientType)
                        .int32(messageType).int32(appId)
                        .int32(imeiLen).int32(readBodyLen).vstring(imei,imeiLen)
                        .vstring(readData, readBodyLen);
                    socket.send(messageReader.pack());
                }
            }

            else if(command == 9999){
                var msg = eval("(" + msgBody + ")");
                console.log(msg)
                console.log(userId)

                if (msg["userId"] == "system") {
                    ta.value = ta.value + "\n 系统:" + msg.data;
                } else if (msg["userId"] == userId) {
                    let msgInfo = msg.data;
                    msgInfo = eval("(" + msgInfo + ")");
                    ta.value = ta.value + "\n" + "自己:" + msgInfo.msgBody;
                } else {
                    let msgInfo = msg.data;
                    msgInfo = eval("(" + msgInfo + ")");
                    ta.value = ta.value + "\n" + msg.toId + ":" + msgInfo.msgBody;
                }
            }
        }

        //连接建立的回调函数
        socket.onopen = function (event) {
            socket.binaryType = "arraybuffer";
            var ta = document.getElementById("responseText");
            ta.value = "连接开启";
        }

        //连接断掉的回调函数
        socket.onclose = function (event) {
            var ta = document.getElementById("responseText");
            ta.value = ta.value + "\n" + "连接关闭";
        }
    } else {
        alert("浏览器不支持WebSocket!");
    }

    //登录
    function login(userId, toUser,clientType,imei,appId) {
        if (!window.WebSocket) {
            return;
        }
        console.log(userId + " " + toUser);
        //当websocket状态打开
        if (socket.readyState == WebSocket.OPEN) {

            var command = 9000;
            var version = 1;
            if(clientType == null){
                clientType = 1;
            }
            clientType = parseInt(clientType);

            var messageType = 0x0;

            if(imei == null){
                imei = "web";
            }

            if(appId == null){
                appId = '10000'
            }
            appId = parseInt(appId);

            var userId = userId;

            var data = {"userId": userId, "appId": 10000, "clientType": clientType, "imei": imei,"customStatus":null,"customClientName":""};
            var jsonData = JSON.stringify(data);
            console.log(jsonData);

            var bodyLen = jsonData.length;
            var imeiLen = getLen(imei);
            let loginMsg = new ByteBuffer();
            loginMsg.int32(command).int32(version)
                .int32(clientType).int32(messageType)
                .int32(appId).int32(imeiLen)
                .int32(bodyLen).vstring(imei,imeiLen)
                .vstring(jsonData, bodyLen);
            socket.send(loginMsg.pack());
        } else {
            alert("连接没有开启");
        }
    }

    // *               私有协议规则,
    // *               4位表示Command表示消息的开始,
    // *               4位表示version
    // *               4位表示clientType
    // *               4位表示messageType
    // *               4位表示appId(待定)
    // *               4位表示数据长度
    // *               后续将解码方式加到数据头根据不同的解码方式解码,如pb,json,现在用json字符串
    //发消息
    function sendMsg(userId, toId, command,msg,clientType,imei,appId) {
        if (!window.WebSocket) {
            return;
        }
        console.log(msg)
        //当websocket状态打开
        if (socket.readyState == WebSocket.OPEN) {
            // debugger;
            //var command = 1103;


            // var command = 9000;
            var version = 1;
            if(clientType == null ){
                clientType = 1;
            }

            clientType = parseInt(clientType);

            var messageType = 0x0;

            var userId = userId;

            if(command == null || command == ''){
                command = 1103;
            }

            if(imei == null){
                imei = "web"
            }
            if(appId == null){
                appId = '10000'
            }
            appId = parseInt(appId);
            var data = {
                "userId": userId,
                "groupId": toId,
                "appId": appId,
                "clientType": 1,
                "imei": imei,
                "command": command
            }

            var messageId = uuid();
            if(msg === 'lld'){
                messageId = msg;
            }
            var messageData = {};
            if(command == 1103){
                messageData = {
                    "messageId": messageId,
                    "fromId": userId,
                    "toId": toId,
                    "appId": appId,
                    "clientType": clientType,
                    "imei": imei,
                    "messageBody": msg
                }
            }else if (command == 2104){
                messageData = {
                    "messageId": messageId,
                    "fromId": userId,
                    "groupId": toId,
                    "appId": appId,
                    "clientType": clientType,
                    "imei": imei,
                    "messageBody": msg
                }
            }else{
                messageData = JSON.parse(msg)
            }
            // data.data = messageData;

            var jsonData = JSON.stringify(messageData);
            console.log(jsonData)
            var bodyLen = getLen(jsonData);
            var imeiLen = getLen(imei);
            let sendMsg = new ByteBuffer();
            sendMsg.int32(command).int32(version)
                .int32(clientType).int32(messageType)
                .int32(appId).int32(imeiLen)
                .int32(bodyLen).vstring(imei,imeiLen)
                .vstring(jsonData, bodyLen);
            console.log(sendMsg.pack());
            socket.send(sendMsg.pack());
        } else {
            alert("连接没有开启");
        }
    }

    function getLen(str) {
        var len = 0;
        for (var i = 0; i < str.length; i++) {
            var c = str.charCodeAt(i);
            //单字节加1
            if ((c >= 0x0001 && c <= 0x007e) || (0xff60 <= c && c <= 0xff9f)) {
                len++;
            } else {
                len += 3;
            }
        }
        return len;
    }


</script>
<form onsubmit="return false">

    <textarea placeholder="输入登录id" id="userId" name="userId" style="width: 100px;height: 20px"></textarea>
    <textarea placeholder="你要发消息给谁" name="toUser" style="width: 100px;height: 20px"></textarea>
    <textarea placeholder="appId" id="appId" name="appId" style="width: 100px;height: 20px"></textarea>
    <textarea placeholder="clientType" id="clientType" name="clientType" style="width: 100px;height: 20px"></textarea>
    <textarea placeholder="imei" id="imei" name="imei" style="width: 100px;height: 20px"></textarea>
    <input type="button" value="login" onclick="login(this.form.userId.value,this.form.toUser.value,this.form.clientType.value
    ,this.form.imei.value,this.form.appId.value);">

    <textarea placeholder="输入command" name="command" style="width: 200px;height: 20px"></textarea>

    <textarea placeholder="输入要发送的内容" name="message" style="width: 200px;height: 20px"></textarea>

    <input type="button" value="发送数据"
           onclick="sendMsg(this.form.userId.value,this.form.toUser.value,this.form.command.value,this.form.message.value
           ,this.form.clientType.value,this.form.imei.value,this.form.appId.value);">

    <h3>服务器输出:</h3>

    <textarea id="responseText" style="width: 400px;height: 300px;"></textarea>

    <input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="清空数据">
</form>
</body>
</html>

群聊

群聊前置校验

CheckSendMessageService#checkGroupMessage

public ResponseVO checkGroupMessage(String fromId, String groupId, Integer appId) {

        // 检查 fromId用户 是否被 禁言 或 禁用
        ResponseVO responseVO = checkSenderForbidAndMute(fromId, appId);
        if (!responseVO.isOk()) {
            return responseVO;
        }

        // 判断群是否存在逻辑
        ResponseVO<ImGroupEntity> group = imGroupService.getGroup(groupId, appId);
        if (!group.isOk()) {
            return group;
        }

        // 判断群成员是否在群内
        ResponseVO<GetRoleInGroupResp> roleInGroupOne = imGroupMemberService.getRoleInGroupOne(groupId, fromId, appId);
        if (!roleInGroupOne.isOk()) {
            return roleInGroupOne;
        }
        GetRoleInGroupResp data = roleInGroupOne.getData();

        // 判断群是否被禁言
        // 如果禁言 只有群管理和群主可以发言
        ImGroupEntity groupData = group.getData();
        if (groupData.getMute() == GroupMuteTypeEnum.MUTE.getCode()
                && data.getRole() != GroupMemberRoleEnum.MANAGER.getCode()
                && data.getRole() != GroupMemberRoleEnum.OWNER.getCode()
        ) {
            return ResponseVO.errorResponse(GroupErrorCode.THIS_GROUP_IS_MUTE);
        }

        // 群成员禁言截止时间
        if (data.getSpeakDate() != null && data.getSpeakDate() > System.currentTimeMillis()) {
            return ResponseVO.errorResponse(GroupErrorCode.GROUP_MEMBER_IS_SPEAK);
        }

        return ResponseVO.successResponse();
    }

GroupMessageService

用于处理群聊消息
第一步:需要作前置校验,校验是否禁用、禁用、群成员是否禁言等
第二步:A将消息发送到群G,A先将此群消息发送到IM服务的tcp层,tcp将此消息通过mq投递给逻辑层,然后逻辑层回ack给tcp,tcp将ack回给A,然后逻辑层通过投递消息给tcp同步给发送方的所有在线端,然后找到群中的所有群成员依次投递消息给tcp同步给接收方

@Service
public class GroupMessageService {

    @Autowired
    CheckSendMessageService checkSendMessageService;

    @Autowired
    MessageProducer messageProducer;

    @Autowired
    ImGroupMemberService imGroupMemberService;

    @Autowired
    MessageStoreService messageStoreService;

    @Autowired
    RedisSeq redisSeq;

    private final ThreadPoolExecutor threadPoolExecutor;

    {
        AtomicInteger num = new AtomicInteger(0);
        threadPoolExecutor = new ThreadPoolExecutor(8, 8, 60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setDaemon(true);
                thread.setName("message-group-thread-" + num.getAndIncrement());
                return thread;
            }
        });
    }

    public void process(GroupChatMessageContent messageContent) {

        String fromId = messageContent.getFromId();
        String groupId = messageContent.getGroupId();
        Integer appId = messageContent.getAppId();

        //前置校验
        //这个用户是否被禁言 是否被禁用
        //发送方和接收方是否是好友
        GroupChatMessageContent messageFromMessageIdCache = messageStoreService.getMessageFromMessageIdCache(
                messageContent.getAppId(),
                messageContent.getMessageId(),
                GroupChatMessageContent.class
        );

        if (messageFromMessageIdCache != null) {
            threadPoolExecutor.execute(() -> {

                //1.回ack成功给自己
                ack(messageContent, ResponseVO.successResponse());

                //2.发消息给同步在线端
                syncToSender(messageContent, messageContent);

                //3.发消息给对方在线端
                dispatchMessage(messageContent);
            });
        }

        long seq = redisSeq.doGetSeq(messageContent.getAppId() + ":" + Constants.SeqConstants.GroupMessage + messageContent.getGroupId());

        messageContent.setMessageSequence(seq);

        threadPoolExecutor.execute(() -> {

            messageStoreService.storeGroupMessage(messageContent);

            List<String> groupMemberId = imGroupMemberService.getGroupMemberId(messageContent.getGroupId(), messageContent.getAppId());
            messageContent.setMemberId(groupMemberId);

            OfflineMessageContent offlineMessageContent = new OfflineMessageContent();
            BeanUtils.copyProperties(messageContent, offlineMessageContent);
            offlineMessageContent.setToId(messageContent.getGroupId());
            messageStoreService.storeGroupOfflineMessage(offlineMessageContent, groupMemberId);

            //1.回ack成功给自己
            ack(messageContent, ResponseVO.successResponse());

            //2.发消息给同步在线端
            syncToSender(messageContent, messageContent);

            //3.发消息给对方在线端
            dispatchMessage(messageContent);

            messageStoreService.setMessageFromMessageIdCache(messageContent.getAppId(), messageContent.getMessageId(), messageContent);
        });
    }

    private void dispatchMessage(GroupChatMessageContent messageContent) {
        for (String memberId : messageContent.getMemberId()) {
            if (!memberId.equals(messageContent.getFromId())) {
                messageProducer.sendToUser(
                        memberId,
                        GroupEventCommand.MSG_GROUP,
                        messageContent,
                        messageContent.getAppId()
                );
            }
        }
    }

    private void ack(MessageContent messageContent, ResponseVO responseVO) {

        ChatMessageAck chatMessageAck = new ChatMessageAck(messageContent.getMessageId());

        responseVO.setData(chatMessageAck);

        //发消息
        messageProducer.sendToUser(
                messageContent.getFromId(),
                GroupEventCommand.GROUP_MSG_ACK,
                responseVO,
                messageContent
        );
    }

    private void syncToSender(GroupChatMessageContent messageContent, ClientInfo clientInfo) {
        messageProducer.sendToUserExceptClient(
                messageContent.getFromId(),
                GroupEventCommand.MSG_GROUP,
                messageContent, messageContent
        );
    }

    private ResponseVO imServerPermissionCheck(String fromId, String toId, Integer appId) {

        ResponseVO responseVO = checkSendMessageService.checkGroupMessage(fromId, toId, appId);

        return responseVO;
    }

    public SendMessageResp send(SendGroupMessageReq req) {

        SendMessageResp sendMessageResp = new SendMessageResp();

        GroupChatMessageContent message = new GroupChatMessageContent();

        BeanUtils.copyProperties(req, message);

        messageStoreService.storeGroupMessage(message);

        sendMessageResp.setMessageKey(message.getMessageKey());

        sendMessageResp.setMessageTime(System.currentTimeMillis());

        //2.发消息给同步在线端
        syncToSender(message, message);

        //3.发消息给对方在线端
        dispatchMessage(message);

        return sendMessageResp;

    }
}

MqMessageProducer

@Slf4j
public class MqMessageProducer {

    public static void sendMessage(Message message, Integer command) {

        Channel channel = null;

        String com = command.toString();

        // 获取指令 首字符, 指令类型
        String commandSub = com.substring(0, 1);
        CommandType commandType = CommandType.getCommandType(commandSub);

        String channelName = "";

        // 根据指令类型, 选择不同的交换机
        if (commandType == CommandType.MESSAGE) {

            // channelName为 pipeline2MessageService
            channelName = Constants.RabbitConstants.Im2MessageService;

        } else if (commandType == CommandType.GROUP) {

            // channelName为 pipeline2GroupService
            channelName = Constants.RabbitConstants.Im2GroupService;

        } else if (commandType == CommandType.FRIEND) {

            // channelName为 pipeline2FriendshipService
            channelName = Constants.RabbitConstants.Im2FriendshipService;

        } else if (commandType == CommandType.USER) {

            // channelName为 pipeline2UserService
            channelName = Constants.RabbitConstants.Im2UserService;
        }

        try {

            // 拿到对应channelName的 Channel 对象
            //(针对每个交换机 都有 1个 Channel对象 与之对应)
            channel = MqFactory.getChannel(channelName);

            JSONObject o = (JSONObject) JSON.toJSON(message.getMessagePack());
            o.put("command", command);
            o.put("clientType", message.getMessageHeader().getClientType());
            o.put("imei", message.getMessageHeader().getImei());
            o.put("appId", message.getMessageHeader().getAppId());

            // 用与交换机对应的channel对象 发送到名为 channelName 的交换机, 路由key为空字符串, 不指定属性, 消息内容
            //(这也就是说: tcp发送到rabbitmq中的上面4个交换机, 都使用针对每个交换机而创建的Channel对象,
            //            而逻辑层则使用了@RabbitListener注解来为这些个交换机绑定了各自的队列, 并消费消息)
            channel.basicPublish(channelName, "", null, o.toJSONString().getBytes());

        } catch (Exception e) {

            log.error("发送消息出现异常:{}", e.getMessage());
        }
    }

    public static void sendMessage(Object message, MessageHeader header, Integer command) {

        Channel channel = null;

        String com = command.toString();

        // 获取指令 首字符, 指令类型
        String commandSub = com.substring(0, 1);
        CommandType commandType = CommandType.getCommandType(commandSub);

        String channelName = "";

        if (commandType == CommandType.MESSAGE) {

            // channelName为 pipeline2MessageService
            channelName = Constants.RabbitConstants.Im2MessageService;

        } else if (commandType == CommandType.GROUP) {

            // channelName为 pipeline2GroupService
            channelName = Constants.RabbitConstants.Im2GroupService;

        } else if (commandType == CommandType.FRIEND) {

            // channelName为 pipeline2FriendshipService
            channelName = Constants.RabbitConstants.Im2FriendshipService;

        } else if (commandType == CommandType.USER) {

            // channelName为 pipeline2UserService
            channelName = Constants.RabbitConstants.Im2UserService;
        }

        try {

            // 拿到对应channelName的 Channel 对象
            channel = MqFactory.getChannel(channelName);

            JSONObject o = (JSONObject) JSON.toJSON(message);
            o.put("command", command);
            o.put("clientType", header.getClientType());
            o.put("imei", header.getImei());
            o.put("appId", header.getAppId());

            // 发送到名为 channelName 的交换机, 路由key为空字符串, 不指定属性, 消息内容
            //(这也就是说: tcp发送到rabbitmq中的上面4个交换机, 都使用各自创建的Channel对象,
            //            而逻辑层则使用了@RabbitListener注解来为这些个交换机绑定了各自的队列, 并消费消息)
            channel.basicPublish(channelName, "", null, o.toJSONString().getBytes());

        } catch (Exception e) {
            log.error("发送消息出现异常:{}", e.getMessage());
        }
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值