


专栏:多用户网页项目开发实录
个人主页:手握风云
目录
[1.1. 模块核心功能](#1.1. 模块核心功能)
[1.2. 数据库设计](#1.2. 数据库设计)
[1. 会话表](#1. 会话表)
[2. 会话-用户关联表](#2. 会话-用户关联表)
[1.3. 约定前后端交互接口](#1.3. 约定前后端交互接口)
[1. 获取会话列表接口](#1. 获取会话列表接口)
[2. 创建会话接口](#2. 创建会话接口)
[1.4. 服务端代码实现](#1.4. 服务端代码实现)
[1. 实体类](#1. 实体类)
[2. Mapper 层](#2. Mapper 层)
[3. API 层](#3. API 层)
[2.1. 会话创建的事务保障](#2.1. 会话创建的事务保障)
[2.2. 会话排序规则](#2.2. 会话排序规则)
[2.3. 会话复用机制](#2.3. 会话复用机制)
一、会话管理模块
1.1. 模块核心功能
会话管理模块是聊天核心枢纽,负责管理用户间的聊天会话、维护会话与用户的关联关系、提供会话列表展示与会话创建能力,为消息收发奠定基础,且预留了群聊扩展空间。
- 维护用户与聊天会话的多对多关联(支持单聊 / 群聊)
- 提供会话列表获取能力,按最后访问时间排序
- 实现会话创建 / 复用逻辑(有则复用,无则新建)
- 绑定会话与好友关系,支撑消息收发与历史记录加载
1.2. 数据库设计
会话模块涉及2 张核心表,采用多对多关联设计。
1. 会话表
sql
-- 会话表
DROP TABLE IF EXISTS message_session;
CREATE TABLE message_session (
sessionId INT PRIMARY KEY AUTO_INCREMENT, -- 会话唯一 ID,主键自增
lastTime DATETIME -- 会话最后访问时间,用于排序
);
INSERT INTO message_session VALUES(1, '2005-03-13 00:00:00');
INSERT INTO message_session VALUES(2, '2004-11-13 00:00:00');
2. 会话-用户关联表
建立会话与用户的多对多关联(一个会话含多个用户,一个用户有多个会话)。
sql
-- 会话-用户关联表
DROP TABLE IF EXISTS message_session_user;
CREATE TABLE message_session_user (
sessionId INT, -- 会话ID
userId INT -- 用户ID
);
INSERT INTO message_session_user VALUES(1, 1), (1, 2);
INSERT INTO message_session_user VALUES(2, 1), (2, 3);
1.3. 约定前后端交互接口
模块定义2 个核心接口,完成会话列表获取与创建。
1. 获取会话列表接口
- 请求:GET /sessionList
- 响应:JSON 数组,包含会话 ID、会话内好友、最后一条消息(预览)
sql
[
{
"sessionId": 1,
"friends": [{"friendId": 2, "friendName": "Bezos"}],
"lastMessage": "晚上吃啥?"
},
{
"sessionId": 2,
"friends": [{"friendId": 3, "friendName": "Musk"}],
"lastMessage": "晚上一起约?"
}
]
2. 创建会话接口
- 请求:POST /session?toUserId=目标用户ID
- 响应:返回新建 / 复用的会话 ID
sql
{"sessionId": 1}
1.4. 服务端代码实现
1. 实体类
- 会话实体
java
package com.yang.chatroom.model;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class MessageSession {
private int sessionId;
private List<Friend> friends;
private String lastMessage;
}
- 会话 - 用户关联实体
java
package com.yang.chatroom.model;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class MessageSessionUserItem {
private int sessionId; // 会话ID
private int userId; // 用户ID
}
2. Mapper 层
- MessageSessionMapper 接口
java
package com.yang.chatroom.model;
import java.util.List;
public interface MessageSessionMapper {
// 1. 根据用户ID获取会话ID,按最后访问时间降序排序
List<Integer> getSessionIdsByUserId(int userId);
// 2. 根据会话ID获取好友列表(排除自己)
List<Friend> getFriendBySessionId(int sessionId, int selfUserId);
}
- MessageSessionMapper.xml
XML
<?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接口全类名一致 -->
<mapper namespace="com.yang.chatroom.model.MessageSessionMapper">
<insert id="addMessageSession">
insert into message_session values (null, now())
</insert>
<insert id="addMessageSessionUser">
insert into message_session_user values (#{sessionId}, #{userId})
</insert>
<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="getFriendBySessionId" resultType="com.yang.chatroom.model.Friend">
select userId as friendId, username as friendName from user
where userId in
(select friendId from message_session_friend where sessionId = #{sessionId} and friendId != #{selfUserId})
</select>
</mapper>
3. API 层
java
package com.yang.chatroom.api;
import com.yang.chatroom.model.*;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@RestController
public class MessageSessionAPI {
@Resource
private MessageSessionMapper messageSessionMapper;
@GetMapping("/sessionList")
@ResponseBody
public Object getMessageSessionList(HttpServletRequest request) {
List<MessageSession> sessionList = new ArrayList<>();
// 获取当前登录用户
HttpSession session = request.getSession(false);
User user = (User) session.getAttribute("user");
// 1. 查询用户的所有会话ID
List<Integer> sessionIds = messageSessionMapper.getSessionIdsByUserId(user.getUserId());
for (Integer sessionId : sessionIds) {
MessageSession msgSession = new MessageSession();
msgSession.setSessionId(sessionId);
// 2. 查询会话内好友
List<Friend> friends = messageSessionMapper.getFriendBySessionId(sessionId, user.getUserId());
msgSession.setFriends(friends);
}
return sessionList;
}
@PostMapping("/session")
@ResponseBody
@Transactional
public Object addMessageSession(int toUserId, HttpServletRequest request) {
HashMap<String, Integer> resp = new HashMap<>();
// 获取当前登录用户(发送方)
HttpSession httpSession = request.getSession(false);
User fromUser = (User) httpSession.getAttribute("user");
// 1. 新建会话
MessageSession session = new MessageSession();
messageSessionMapper.addMessageSession(session);
int sessionId = session.getSessionId();
// 2. 关联发送方道会话
MessageSessionUserItem item1 = new MessageSessionUserItem();
item1.setUserId(fromUser.getUserId());
item1.setSessionId(sessionId);
messageSessionMapper.addMessageSessionUser(item1);
// 3. 关联接收方道会话
MessageSessionUserItem item2 = new MessageSessionUserItem();
item2.setUserId(toUserId);
item2.setSessionId(sessionId);
messageSessionMapper.addMessageSessionUser(item2);
resp.put("sessionId", sessionId);
return resp;
}
}
二、核心逻辑与关键细节
2.1. 会话创建的事务保障
创建会话时,新增会话 + 关联两个用户是原子操作,必须加@Transactional,避免数据不一致。
2.2. 会话排序规则
按 lastTime (最后访问时间)降序排列,最新会话永远在列表顶部。
2.3. 会话复用机制
点击好友时,先查找已有会话,避免重复创建;无会话才新建,提升体验。