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

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

相关推荐
xszivvvv几秒前
Mariadb数据库frm文件异常,如何恢复数据
数据库·mariadb
2601_9628857216 分钟前
quotes 字段全解:涨跌幅、振幅、换手率的口径与单位
java·前端·spring
excel38 分钟前
encodeURI 和 encodeURIComponent 有什么区别?
前端
smile_life_38 分钟前
MySQL 设置 防止账户被暴力破解
数据库·mysql
前端精髓41 分钟前
调用openrouter的免费API模型
java·前端·javascript
qetfw43 分钟前
UOS DHCP Relay:跨网段分配与 IP、DNS、网关验证
linux·服务器·网络·tcp/ip·uos
ShineWinsu1 小时前
对于Qt:QRadioButton常见属性以及QButtonGroup的解析
数据库·c++·qt·microsoft·gui·qradiobutton·qbuttongroup
赛博守夜人1 小时前
AI安全攻防与治理之十四:AI 安全测试不能脱离传统安全,Web、API、移动端与模型攻击面的组合拳
前端·人工智能·安全
开开心心_Every1 小时前
快速隐藏程序工具自定义快捷键一键切换
java·开发语言·前端·spring·智能手机·word·intellij-idea
测试盐1 小时前
oracle数据库卡顿排查
数据库·oracle