基于 Java 的网页聊天室(三)

专栏:多用户网页项目开发实录

个人主页:手握风云

目录

一、会话管理模块

[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. 提供会话列表获取能力,按最后访问时间排序
  3. 实现会话创建 / 复用逻辑(有则复用,无则新建)
  4. 绑定会话与好友关系,支撑消息收发与历史记录加载

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. 会话复用机制

点击好友时,先查找已有会话,避免重复创建;无会话才新建,提升体验。

相关推荐
AlienZHOU2 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
2501_933670792 小时前
2026秋招数据分析岗备考路线:SQL、BI、项目与面试题拆解
数据库
国际云,接待4 小时前
华为云 EVS 性能诊断:用 CES、ECS QoS 与 fio 定位 IOPS 和吞吐瓶颈
服务器
我要见SA姐15 小时前
告别 Copilot?Codex 本地化部署指南
运维·数据库·机器学习·oracle·回归
Captaincc5 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
xcLeigh6 小时前
聊聊国产化替换:好用数据迁移工具KDMS怎么帮咱们搞定评估难
数据库·sql·数据迁移·kes·kdms
Elastic 中国社区官方博客6 小时前
列式存储并不等同于列式数据库。Columnar 模式为 Elasticsearch 带来了什么
大数据·运维·数据库·elasticsearch·搜索引擎
我要见SA姐17 小时前
用 Claude Code 重构遗留系统:从评估到落地的完整实践指南
数据库·ide·vscode·oracle·编辑器
计算机魔术师7 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
智恒百亿7 小时前
RTX 5090 八卡服务器 vs RTX PRO 6000 整机:AI 推理、微调与渲染选型部署指南
运维·服务器·rtx5090