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

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

相关推荐
LcVong2 小时前
MySQL 5.2/5.7 开启Binlog日志详细步骤(附验证+查看+恢复)
数据库·mysql·adb
weixin199701080162 小时前
《识货商品详情页前端性能优化实战》
前端·性能优化
Forever7_2 小时前
重磅!Vue3 手势工具正式发布!免费使用!
前端·前端框架·前端工程化
用户806138166592 小时前
发布为一个 npm 包
前端·javascript
开开心心_Every2 小时前
限时免费加密、隐藏、锁定文件文件夹好工具
运维·服务器·人工智能·edge·pdf·逻辑回归·深度优先
FL4m3Y4n2 小时前
MySQL缓存策略
数据库·mysql·缓存
wsx_iot2 小时前
TDengine学习
数据库·学习·tdengine
树上有只程序猿2 小时前
低代码何时能出个“秦始皇”一统天下?我是真学不动啦!
前端·后端·低代码
TT_哲哲2 小时前
小程序双模式(文件 / 照片)上传组件封装与解析
前端·javascript