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

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

相关推荐
Csvn7 小时前
useRef 的 5 个冷门但救命的高级用法
前端
小小小小宇7 小时前
Harness Engineering 与 AI 联动
前端
鱼人7 小时前
HTML5 页面性能优化大全
前端
ping某7 小时前
专栏-null 和 undefined 到底是什么?
前端·javascript·后端
用户900463370407 小时前
5MB vs 4KB vs 无限大:浏览器存储谁更强?
前端
小小小小宇8 小时前
Harness Engineering 全解析与应用
前端
牧艺8 小时前
cos-design v3.0:从 15 个 Demo 到 49 个组件的视觉特效库
前端·视觉设计
lichenyang4538 小时前
ASCF 架构升级总览:WebRuntimePage 为什么要变薄
前端
道友可好8 小时前
从今天开始:你的第一个 Harness Engineering 实践
前端·人工智能·后端
Linsk8 小时前
组件 = 模板 + 业务逻辑
java·前端·vue.js