IM系统第四章 -- 用户与好友通信

IM系统第四章 – 用户与好友通信

你是如何完成这个功能的?

实现该功能的流程是:

用户与好友通信的前提是:你已经点开了和好友的对话框(上一节已经实现了)。那么你就相当于获取到了好友ID,当用户点击发送消息,就会将好友ID,用户ID,消息内容,时间一并发给服务端;服务端会先将消息利用多线程的方式进行异步落库存储;接下来服务端会判断好友是否在线,判断的标准是Map中是否有好友的通信的管道Channel;如果获取到好友的Channel后,将消息发给好友,然后通过UI接口,将消息显示在界面上。

上述描述的流程如下:(参照 小傅哥

img

实现功能所需的处理:

  1. 自定义协议;
  2. UI事件实现;
  3. 服务端与好友通信功能处理;
  4. 客户端与好友通信功能处理;

自定义协议:消息请求 协议、消息请求应答 协议

// 消息请求 协议
public class MsgRequest extends Packet {
    private String userId;   // 用户Id[自己的]
    private String friendId; // 好友Id
    private String msgText;  // 消息内容
    private Integer msgType; // 消息类型
    private Date msgDate;    // 消息时间
    构造函数...
    get()/set()...
}

// 消息请求应答 协议
public class MsgResponse extends Packet {
    private String friendId; // 好友ID[对方]
    private Integer msgType; // 消息类型;0文字消息、1固定表情
    private String msgText;  // 传输消息内容
    private Date msgDate;    // 消息时间
    构造函数...
    get()/set()...
}

自定义完协议后,完成客户端需要点击 ”发送消息“才能真正发送消息,那么这是一个事件行为

/**
     * 消息发送
     *
     * @param userId   用户Id
     * @param talkId   对话Id(好友ID/群组ID)
     * @param talkType 对话框类型;0好友、1群组
     * @param msg      发送消息内容
     * @param msgType  消息类型;0文字消息、1固定表情
     * @param msgDate  发送消息时间
     */
    @Override
    public void doSendMsg(String userId, String talkId, Integer talkType, String msg, Integer msgType, Date msgDate) {
        Channel channel = BeanUtil.getBean("channle", Channel.class);
        if(talkType == 0) {
            channel.writeAndFlush(new MsgRequest(userId, talkId, msg, msgType, msgDate));
        }
        if(talkType == 1) {
            channel.writeAndFlush(new MsgGroupRequest(userId, talkId, msg, msgType, msgDate));
        }
    }

接下来来到客户端对消息请求的处理:主要是将传送过来的 消息请求应答 协议的内容传给UI模块的接口作为参数,然后显示在界面上;

// 客户端消息处理器
public class MsgHandler extends MyBizHandler<MsgResponse> {

    private UIService uiService;
    public MsgHandler(UIService uiService) {
        super(uiService);
    }

    @Override
    public void channelRead(Channel channel, MsgResponse msg) {
        IChatMethod chat = uiService.getChat();
        Platform.runLater(()->{
            /**
             * 填充对话框消息 --好友[别人的消息]、
             * 好友的Id
             * 好友发的消息
             * 好友发的消息类型
             * 好友发消息的时间
             * 是否设置为首位
             * 是否被选中
             * 是否提醒
             */
            chat.addTalkMsgUserLeft(msg.getFriendId(), msg.getMsgText(), msg.getMsgType(), msg.getMsgDate(),
                    true, false, true);
        });
    }
}

接下来就来到了服务端,服务端主要负责的事情是:将传来的 消息请求协议的内容进行处理,其中包括消息异步落库,然后才是去判断对方用户的channel是否在线,然后才将信息传入给对方channel中让对方接收并显示在客户端上,这也是实现了离线消息的存储和转发的问题。

public class MsgHandler extends MyBizHandler<MsgResponse> {
    @Override
    public void channelRead(Channel channel, MsgRequest msg) {
        // 利用多线程的方式 异步落库
        userService.asyncAppendChatRecord(new ChatRecordInfo(msg.getUserId(), msg.getFriendId(), msg.getMsgText(), msg.getMsgType(), msg.getMsgDate()));
        // 添加对话框[如果对方没有你的对话框则添加]
        userService.addTalkBoxInfo(msg.getFriendId(), msg.getUserId(), Constants.TalkType.Friend.getCode());
        // 获取好友通道
        Channel friendChannel = SocketUtil.getChannel(msg.getFriendId());
        if(null == friendChannel) {
            return;
        }
        // 发送消息
        friendChannel.writeAndFlush(new MsgResponse(msg.getUserId(), msg.getMsgText(), msg.getMsgType(), msg.getMsgDate()));
    }
}

那么问题又来了?**这个异步落库是如何实现的呢?**无非就是开启一个线程的事情,然后该线程执行将消息通过mybatis框架的 Insert into 操作将消息入库。(虽然看起来很简单,但实现的类层级有点复杂,解耦太多哈哈自己看源码吧)

// 在UserService该接口中定义一个asyncAppendChatRecord()方法
public interface UserService {
	void asyncAppendChatRecord(ChatRecordInfo chatRecordInfo);
}

// UserServiceImpl中
public class UserServiceImpl implements UserService {
    @Resource
    private IUserRepository userRepository; // 类似于数据存储中间站
    @Resource
    private ThreadPoolExecutor taskExecutor;

    // 默认线程池,线程池的线程数为4
    private static ExecutorService executorService = Executors.newFixedThreadPool(4);
    /**
     * 异步添加聊天记录
     * @param chatRecordInfo 聊天记录信息
     */
    @Override
    public void asyncAppendChatRecord(ChatRecordInfo chatRecordInfo) {
        taskExecutor.execute(new Runnable(){
            @Override
            public void run(){
                userRepository.appendChatRecord(chatRecordInfo);
            }
        });
    }
}

public interface IUserRepository {
     // 添加聊天记录
     void appendChatRecord(ChatRecordInfo chatRecordInfo);
}
@Repository("userRepository")
public class UserRepository implements IUserRepository {
    @Autowired
    private IChatRecordDao chatRecordDao;
    @Override
    public void appendChatRecord(ChatRecordInfo chatRecordInfo) {
        ChatRecord chatRecord = new ChatRecord();
        // 给消息记录对象填充属性
        chatRecord.setUserId(chatRecordInfo.getUserId());
        chatRecord.setFriendId(chatRecordInfo.getFriendId());
        chatRecord.setMsgContent(chatRecordInfo.getMsgContent());
        chatRecord.setMsgType(chatRecordInfo.getMsgType());
        chatRecord.setMsgDate(chatRecordInfo.getMsgDate());
        chatRecord.setTalkType(chatRecordInfo.getTalkType());
        // 将填充好属性的消息对象传送给dao层对象
        chatRecordDao.appendChatRecord(chatRecord);
    }
}

真正执行操作数据库的接口类是IChatRecordDao

@Mapper
public interface IChatRecordDao {
    void appendChatRecord(ChatRecord req); // mybatis中id的名字就是该方法的名字
}

总结

实现用户与好友的通信功能:

  1. 首先自定义通信协议:MsgRequestMsgResponse
  2. 第二个因为你要按 ”发送消息“ ,所以也需要定义事件行为,当点击该按钮的时候,就会发送消息请求协议过去;
  3. 来到服务端:其步骤是对消息进行处理,先是将消息异步存储,同时去查询好友的channel管道是否在线,如果在线就获取到好友的channel管道直接发送消息过去即可;
  4. 至于这个将消息异步落库的操作:用ThreadPoolTaskExecutor开启一个线程,然后将数据传输对象传输过去,因为在服务端中包装不是数据库聊天记录表中的消息对象,而是消息传输对象;所以需要对象转换,转换完成后就可以用Mybatis入库了完成消息存储了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值