实现功能
- 用户注册和登录
- 好友列表展示
- 会话列表展示: 显示当前正在进行哪些会话 (单聊 / 群聊) , 选中好友列表中的某个好友, 会生成对应的会话
- 实时通信, A给B发送消息, B的聊天界面 / 会话界面能立刻显示新的消息
TODO:
- 添加好友功能
- 用户头像显示
- 传输图片 / 表情包
- 历史消息搜索
- 消息撤回
- …
相关技术
网络通信: WebSocket
Spring + SpringBoot + SpringMVC + MyBatis
HTML + CSS + JS
数据库设计
项目的基本框架
前端页面
注册和登录页面
聊天界面
后端代码
实体类
User
本类表示一个用户的信息, 对应数据库的 user 表
@Data public class User { private int userId; private String username = ""; private String password = ""; public User() { } public User(String username, String password) { this.username = username; this.password = password; } }
Friend
使用一个 Friend 对象表示一个好友
// 使用一个 Friend 对象表示一个好友, 对应数据库的 friend 表 @Data public class Friend { private int friendId; private String friendName; public Friend() { } public Friend(int friendId, String friendName) { this.friendId = friendId; this.friendName = friendName; } }
Message
本类表示一条消息的相关信息, 对应数据库的表 message + 字段: fromname
(没有 postTime 是因为: 在查询的时候就是一次性查出所有的时间, 按照时间结果排序后返回, 我们这里就不需要再获取时间了)
// 本类表示一条消息的相关信息 // (没有 postTime 是因为: 在查询的时候就是一次性查出所有的时间, 按照时间结果排序后返回, 我们这里就不需要再获取时间了) @Data public class Message { private Integer messageId; private int fromId; private String fromName; private int sessionId; private String content; public Message() { } public Message( int fromId, String fromName, int sessionId, String content) { this.fromId = fromId; this.fromName = fromName; this.sessionId = sessionId; this.content = content; } }
MessageSession
使用该类表示一个会话, 对应数据库的 message_session + message_session_user
// 使用该类表示一个会话 @Data public class MessageSession { private int sessionId; private List<Friend> friends; private String lastMessage; }
MessageSessionUserItem
该类对象表示 message_session_user 表里的一个记录
// 该类对象表示 message_session_user 表里的一个记录 @Data public class MessageSessionUserItem { private int sessionId; private int userId; public MessageSessionUserItem() { } public MessageSessionUserItem(int sessionId, int userId) { this.sessionId = sessionId; this.userId = userId; } }
MessageRequest
WebSocket 请求
自定义格式, 用于网络通信中接受请求
// WebSocket请求 @Data public class MessageRequest { private String type = "message"; private int sessionId; private String content; }
MessageResponse
WebSocket 响应
自定义格式, 用于网络通信中返回响应
// WebSocket响应 @Data public class MessageResponse { private String type = "message"; private int fromId; private String fromName; private int sessionId; private String content; public MessageResponse() { } public MessageResponse(int fromId, String fromName, int sessionId, String content) { this.fromId = fromId; this.fromName = fromName; this.sessionId = sessionId; this.content = content; } }
数据库
FriendMapper
用户好友的相关操作
@Mapper public interface FriendMapper { // 查询用户好友列表 List<Friend> selectFriendList(@Param("userId") int userId); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.java_chatroom.model.FriendMapper"> <select id="selectFriendList" resultType="com.example.java_chatroom.model.Friend"> select userId as friendId, username as friendName from user where userId in (select friendId from friend where userId = #{userId}) </select> </mapper>
MessageMapper
消息的相关操作
@Mapper public interface MessageMapper { // 获取指定会话的最后一条消息 String getLastMessageBySessionId(@Param("sessionId") int sessionId); // 获取指定会话的历史消息 (限制100条) List<Message> getMessagesBySessionId(@Param("sessionId") int sessionId); // 插入一条消息到数据库表中 void add(@Param("message") Message message); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.java_chatroom.model.MessageMapper"> <select id="getLastMessageBySessionId" resultType="java.lang.String"> select content from message where sessionId = #{sessionId} order by postTime desc limit 1 </select> <select id="getMessagesBySessionId" resultType="com.example.java_chatroom.model.Message"> select messageId, sessionId, fromId, content, username as fromName from message, user where sessionId = #{sessionId} and fromId = userId order by postTime desc limit 100 offset 0 </select> <insert id="add"> insert into message values(null, #{message.fromId}, #{message.sessionId}, #{message.content}, now()); </insert> </mapper>
MessageSessionMapper
会话的相关操作
@Mapper public interface MessageSessionMapper { // 1.根据 userId 获取到该用户在哪些会话中存在, 返回结果是一组 sessionId. List<Integer> getSessionIdsByUserId(@Param("userId") int userId); // 2. 根据 sessionId 查询这个会话包含哪些用户(刨除掉最初的 user) List<Friend> getFriendsBySessionId(@Param("sessionId") int sessionId,@Param("selfUserId") int selfUserId); // 3. 新增会话记录, 返回会话 id int addMessageSession(@Param("messageSession") MessageSession messageSession); // 4.给 message_session_user 表新增对应记录 int addMessageSessionUser(@Param("messageSessionUserItem") MessageSessionUserItem messageSessionUserItem); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.java_chatroom.model.MessageSessionMapper"> <select id="getSessionIdsByUserId" resultType="java.lang.Integer"> select sessionId from message_session where sessionId in ( select sessionId from message_session_user where userId = #{userId} ) order by lastTime desc </select> <select id="getFriendsBySessionId" resultType="com.example.java_chatroom.model.Friend"> select userId as friendId, username as friendName from user where userId in ( select userId from message_session_user where sessionId = #{sessionId} and userId != #{selfUserId} ) </select> <insert id="addMessageSession" useGeneratedKeys="true" keyProperty="messageSession.sessionId"> insert into message_session values(null, now()) </insert> <insert id="addMessageSessionUser"> insert into message_session_user values( #{messageSessionUserItem.sessionId}, #{messageSessionUserItem.userId} ) </insert> </mapper>
从 0 开始实现一个网页聊天室 (小型项目)(下):https://developer.aliyun.com/article/1520799