ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Spring Boot的桌面聊天室系统设计与实现全解析

基于Spring Boot的桌面聊天室系统设计与实现全解析 简介一套基于Spring Boot的桌面聊天室毕业设计项目包含完整源码与SQL数据库脚本适合Java学习者、毕业设计学生以及希望快速上手Spring Boot实战的开发者。压缩包共82个文件大小约4.24MB主要涵盖Java源码、class编译文件、Jar依赖库、SQL建表脚本及少量界面图片其中SQL脚本可直接导入MySQL目录结构清晰便于逐模块对照学习。目前已有428人学习浏览。整个项目围绕Spring Boot自动配置、Spring MVC请求处理、WebSocket实时通信、数据库表设计以及登录安全控制等关键知识点展开配合源码和数据库脚本可帮助读者理解聊天室后端从用户认证到消息存储的完整实现思路。源码中体现的异常处理与项目打包部署方式也为后续二次开发和上线部署提供直接参考适合作为课程设计或毕业设计的起步模板可直接运行或改造。1. 这个“源码数据库”的Spring Boot桌面聊天室项目实际在做一个什么样的系统从标题看很多人会先问一句“聊天室为什么不用Node.js而是拿Spring Boot做桌面客户端”这其实是毕业设计里最常见的一类选题技术栈要求走Java又必须把桌面端、服务端和数据库三样东西都体现出来。这个项目的本质是一个用Spring Boot充当后端服务、用Swing或JavaFX充当桌面客户端、再用MySQL存聊天记录和用户信息的单体应用。它要解决的不是百万人在线的高性能问题而是“用户注册、登录、好友列表、一对一聊天、群聊和消息记录查询”这一整套业务闭环。对IT从业者来说它的价值在于把Spring Boot四层架构、WebSocket推送、数据库连接池和桌面客户端网络编程串在了一条线上真正难的不是运行压缩包里那个源码SQL文件而是看懂后端哪个接口对应客户端哪个按钮、数据库里哪张表存的是历史消息。下面就从拆包开始讲。2. 拆开压缩包Spring Boot工程四层结构与桌面客户端的连接方式2.1 先看项目骨架再谈代码拿到基于spring boot的桌面聊天室系统设计与实现源码数据库.7z第一步不是双击运行而是把压缩包里的目录结构看明白。绝大多数这类毕业设计压缩包解压后是三个部分一个server目录Spring Boot后端工程、一个client目录Swing/JavaFX桌面端、一个db或sql目录数据库脚本。如果你拿到的压缩包只给了后端和SQL那桌面端通常会以一个独立的Maven工程形式放在另一个目录说明文档里会写清导入IDE的步骤。后端工程内部分层一般是这样src/main/java/com/example/chat ├── controller/ // 接收HTTP请求返回JSON ├── service/ // 业务逻辑登录校验、消息落库 ├── mapper/ // MyBatis的Mapper接口 ├── entity/ // 数据库实体类 ├── config/ // WebSocket、跨域、拦截器配置 └── ChatApplication.java这个分层就是常说的“Spring Boot四层架构”Controller层只做参数接收和结果封装Service层写业务规则Mapper层写SQLEntity层映射表结构。桌面客户端不直接操作数据库它只调Controller暴露的REST接口以及连接WebSocket做实时消息接收。这样设计的好处是毕业后你想把桌面端改成小程序端后端一行不用动。2.2 桌面端与后端的通信方式REST WebSocket 双通道聊天室里有两类数据一类是低频但需要立即回执的操作比如登录、注册、加好友另一类是高频的聊天消息。对于前者用普通HTTP的REST接口就够了客户端用HttpURLConnection或OkHttp发POST请求服务器返回JSON。对于后者如果一直用轮询服务器压力大且消息延迟明显所以多数实现会让Spring Boot集成WebSocket协议桌面端通过WebSocket长连接收发实时消息。需要注意Spring Boot的WebSocket和Spring MVC不冲突可以在一个工程里共用端口。配置好之后REST接口走的是/api/**WebSocket走的是/ws/chat一个负责控制流一个负责数据流。2.2.1 最小可用的WebSocket配置类在Spring Boot 2.7/3.x里实现WebSocket有两种方式一种是用ServerEndpoint注解另一种是继承WebSocketConfigurer。毕业设计里我一般建议用WebSocketConfigurer因为你能同时拿到握手前后的拦截器方便在后面加登录校验。它的配置代码很短Configuration EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new ChatWebSocketHandler(), /ws/chat) .setAllowedOrigins(*) .addInterceptors(new ChatHandshakeInterceptor()); } }这段配置声明了一个URL为/ws/chat的WebSocket服务端点。setAllowedOrigins(*)表示允许任意来源连接桌面客户端没有浏览器同源策略所以放开也没问题但如果以后要接Web前端建议改成具体域名。ChatHandshakeInterceptor会在握手前执行可以在里面校验请求参数里带的token拦截掉未登录的连接。2.3 Spring Boot工程目录规范别把客户端代码塞进后端这类项目的另一个常见问题是目录混乱。有些同学会把Swing界面代码直接放在src/main/java下和后端Controller混在一起。这样虽然能跑但当你后面想换一个Web端时就得把界面代码全部拆出来。正确做法是让后端工程保持一个纯粹的服务端角色桌面端单独建一个Maven或Gradle工程只依赖HTTP和WebSocket客户端库。桌面端技术栈没有强制性要求Swing对低版本JDK兼容好、上手快JavaFX界面更现代但在自定义聊天气泡时也要多写代码。不管用哪种网络传输层都是同一个逻辑用一个ChatClient类封装REST登录和WebSocket连接界面只管拿到消息后刷新列表。这种“前后端分离”的思路放到桌面聊天室的语境里就是你在毕业答辩里可以理直气壮解释的那句话客户端只做展示和交互所有状态和数据都以服务端为准。3. 登录、会话与消息收发WebSocket和REST接口的配合3.1 登录接口的典型设计与密码处理先看REST这边。聊天室第一个要用的接口是登录。常见的user表里有user_id、username、password、nickname、avatar。密码不能用明文至少要用BCrypt或SHA-256加盐存储。Spring Boot工程里可以用spring-security-crypto提供的BCryptPasswordEncoder只需要引入依赖不用把Spring Security整套认证机制引进来不然反而会把毕业设计项目复杂度抬太高。PostMapping(/api/login) public Result login(RequestBody LoginRequest request) { User user userService.findByName(request.getUsername()); if (user ! null passwordEncoder.matches(request.getPassword(), user.getPassword())) { String token UUID.randomUUID().toString().replace(-, ); onlineUserService.put(token, user.getUserId()); return Result.ok(token); } return Result.error(用户名或密码错误); }这里的onlineUserService是自定义的一个Map实现作用是把登录成功的token和用户ID做临时绑定。为何要用token而不是直接传用户ID因为WebSocket握手时你只能放下参数或Header传一个随机token比传用户ID安全得多。真正项目里会换成Redis但毕业设计里一个ConcurrentHashMap足够。3.2 用户列表和会话列表先搞清楚这两种列表数据的区别登录进去后聊天室页面上通常有“在线用户”和“最近会话”两个区域。在线用户来自onlineUserService里活跃WebSocket会话对应的用户最近会话来自数据库里的conversation表。这两个数据一个放在内存一个放在MySQL接口设计时也要分开。获取在线用户GET /api/online/list返回当前在线的用户昵称列表。获取最近会话GET /api/conversation/list?userIdxx返回该用户参与的所有会话及最后一条消息摘要。提示很多毕业设计会在“在线用户”这个功能点背后用数据库查询每5秒刷一次。这样做的缺点是用户关掉窗口后状态不能及时清除。更简单的做法是监听WebSocket的afterConnectionClosed事件在会话关闭时把用户从在线Map里移除。3.3 消息从A到B的完整链路当用户A向用户B发送一句话消息的落地流程是这样的桌面客户端通过WebSocket发送一条JSON消息。后端ChatWebSocketHandler的handleTextMessage方法拿到文本。解析JSON判断targetType是单聊还是群聊。如果是单聊先查B用户是否在线在线则直接通过B的WebSocket会话推送过去。无论B在不在线都要把消息保存到message表方便下次拉取历史记录。下面这段是Handler里保存消息并转发的核心逻辑Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { ChatMessage msg objectMapper.readValue(message.getPayload(), ChatMessage.class); msg.setFromUserId(currentUserId(session)); // 消息落库 messageService.save(msg); // 查找接收方的WebSocketSession WebSocketSession targetSession socketSessionRegistry.get(msg.getToUserId()); if (targetSession ! null targetSession.isOpen()) { targetSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(msg))); } }这段代码的要点是currentUserId(session)因为握手时已经在拦截器里把用户ID写入了WebSocketSession的attributes所以这里能直接取到。如果你在拦截器里没做这一步那Handler里就不知道是谁发的消息这是很多运行报“空指针”的原因。socketSessionRegistry是一个与在线用户Map配套的ConcurrentHashMapInteger, WebSocketSession保存着每个用户当前的连接会话。3.4 消息格式设计别只放“内容”两个字段聊天消息的JSON格式设计看起来简单实际很容易漏字段。一个能用的消息体至少要包含这些字段类型说明msgIdLong消息ID前端生成或由后端自动生成fromUserIdInteger发送者用户IDtoUserIdInteger接收者用户ID群聊时用它表示会话IDcontentString文本内容msgTypeInteger0文本1图片2系统提示sendTimeLong客户端发送时间戳毫秒isGroupBoolean是否群聊消息去掉任何一个字段后面的“已读”“撤回”“按时间拉取历史记录”都会变得别扭。尤其要保存sendTime而且建议用客户端时间而不是服务端时间否则用户A和用户B在不同机器上看到的排序会不一致。排序问题靠的是消息ID的自增趋势而不是发送时间。4. 数据库不是附属品表结构、连接配置与初始化数据4.1 聊天室系统的核心表结构这个项目的数据库是整个系统的地基可很多同学把它当成“最后一步”先把代码跑通了再回头建表结果字段对不上就反复改Mapper。根据标题里“源码数据库”这个描述SQL脚本应该是单独提供的你要做的是先看脚本再启动后端。一个常规聊天室的表至少有这几张。以用户表和消息表为例CREATE TABLE user ( user_id int NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL, password varchar(100) NOT NULL, nickname varchar(50) DEFAULT NULL, avatar varchar(255) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (user_id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE message ( msg_id bigint NOT NULL AUTO_INCREMENT, from_user_id int NOT NULL, to_user_id int NOT NULL, is_group tinyint NOT NULL DEFAULT 0, content text, msg_type tinyint DEFAULT 0, send_time bigint NOT NULL, PRIMARY KEY (msg_id), KEY idx_to_user_send_time (to_user_id, send_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有两个容易踩的坑。第一user表在MySQL里是关键字建表一定要执行带反引号的语句否则SQL直接报错。第二message表的send_time用bigint存时间戳不要用datetime因为客户端发送消息会先把时间戳放进JSON服务端落库时直接存数字即可查询历史记录时也可以直接用大于小于比较省掉一次类型转换。当Service层调用Mapper时数据库增删改查都写在接口方法里你在源码里搜索Select、Insert、Update就能把Controller和SQL一一对上。这种做法也方便你在答辩时讲清“一条消息从界面到表里的路径”。4.2 连接配置application.yml里的关键参数Spring Boot读取数据库连接的地方在src/main/resources/application.yml。下面这份配置需要按你本机的MySQL情况修改spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/chatroom?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: yourpassword web: websocket: enabled: true这段配置最值得解释的是URL里的参数。characterEncodingutf8决定字符串写入数据库时的字符集如果你的建表语句里已经写了utf8mb4这里保持一致就不会出现中文乱码serverTimezoneAsia/Shanghai解决MySQL驱动8.x版本把CST时区解析成美国中部时间的问题。allowPublicKeyRetrievaltrue只有在MySQL 8.0以上且用户使用caching_sha2_password认证时才需要加了能避免连接时抛出“Public Key Retrieval is not allowed”。如果你在某个新版本Spring Boot里找不到DataSourceAutoConfiguration不要慌那属于自动配置类包名变化和你的业务代码没关系。只要spring-boot-starter-jdbc或mybatis-spring-boot-starter在依赖里数据源就能自动创建。4.3 初始化数据与数据库迁移压缩包里那个chatroom.sql通常不只是建表语句还会插几个测试账号。你直接用Navicat或命令行执行它mysql -u root -p chatroom chatroom.sql输入密码后如果表已经存在脚本里应该用DROP TABLE IF EXISTS开头否则会报“Table already exists”。毕业设计里用这种方式初始化没问题但如果你打算把项目继续演进建议换成Flyway或Liquibase做迁移管理这样后续加字段时不用再手写ALTER语句。注意dbx数据库工具或者“数据库同步软件”这类工具可以帮你把本机库和服务器库保持一致但毕业设计只要守住一条原则源码里application.yml用的库名、用户名、密码必须和交付的SQL脚本能对上。我见过很多项目压缩包里的配置连的是root/123456而SQL脚本里却建了另一个密码验收时当场翻车。4.4 为什么“数据库同步软件”在这里不是必需品项目访问数据库的常见做法是直连MySQL不需要额外工具。但如果你需要在多台电脑上演示桌面端打包后的环境里不一定有MySQL此时可以用H2数据库做演示模式通过application-demo.yml切换数据源。H2的兼容模式能识别MySQL建表语法只要把spring.datasource.url改成内存地址再加一行spring.datasource.driver-class-nameorg.h2.Driver即可。不过这个方法有个前提SQL脚本里的表名和列名不能用MySQL特有类型例如engineInnoDB这种语句在H2里会报错。建议真有这种需求时先把脚本里的engine和charset这两行删掉再在H2控制台里执行一遍验证。否则你会发现本地跑得好好的换环境后连表都建不出来。5. 从能跑到做好状态管理、异常处理与接口安全5.1 退出登录的坑不要只关客户端窗口桌面聊天室最典型的逻辑漏洞是“退出登录”只调用System.exit(0)。用户关掉窗口时WebSocket连接会断开但REST层面没有告诉服务端“我要下线”。正确做法是在窗口关闭事件里发送一个WebSocket通知比如{action:offline,fromUserId:1}后端收到后把该用户从在线列表移除。如果你想更笨但更可靠就在Handler的afterConnectionClosed里做清理Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { Integer userId (Integer) session.getAttributes().get(userId); if (userId ! null) { socketSessionRegistry.remove(userId); onlineUserService.remove(userId); // 广播用户下线事件通知其他客户端刷新在线列表 } }这段代码把连接关闭和业务状态解除绑在一起。你在客户端测试时可以右键“关闭窗口”正常退出也可以直接用任务管理器强制结束进程再回到另一台已登录的窗口看看在线列表是否都能及时刷新。这部分在线状态管理也是Java面试里问“WebSocket连接可靠性”时经常被追问的点。5.2 消息丢失与重连策略WebSocket连接是长连接网络抖动时会断。断开期间用户发的消息如果直接丢弃体验会很差。常见做法是在客户端维护一个“待发送队列”断线重连成功后重新发送服务端配合一个msg_id去重防止同一条消息被处理两次。if (reconnecting) { pendingQueue.offer(msg); } else { chatClient.send(msg); }reconnecting这个布尔值由客户端的心跳检测决定。服务端在Handler里可以每30秒发一个ping客户端收到后回pong如果连续两次收不到pong服务端主动关闭旧连接然后客户端发起重连。重连时握手地址要和首次连接保持一致但不建议把固定token硬编码在重连逻辑里每次握手都重新走一遍登录校验刷新token再连才能避免token泄漏。5.3 图片与文件消息的落库策略如果聊天室只做文本消息表设计得很简单一旦加图片问题就来了。图片二进制存MySQL会用BLOB但会把消息表撑大且查询性能下降。常见的做法是把图片存到本地磁盘或OSS然后在content里存一个/upload/123.jpg这样的相对路径客户端展示时拼接成完整URL。Spring Boot提供静态资源配置但需要显式开放上传目录的映射Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadDir); }uploadDir可以是./upload/确保服务器运行的工作目录下有这个文件夹。如果你用.7z压缩包交付最好把upload目录和SQL脚本放在压缩包里避免演示时头像加载不出来。另外上传接口要限制文件大小和类型否则一个超大文件就能把服务端打崩。5.4 安全边界Spring Boot Actuator暴露情况要检查毕业设计里常常引入spring-boot-starter-actuator用来查看健康状态但如果你不小心把端点全都暴露了别人就能通过/actuator/env看到你的数据源连接串。在你的application.yml里要限制暴露范围management: endpoints: web: exposure: include: health,info这是一个很小的配置却经常被忽略。还有在WebSocket握手拦截器里校验token时不要只判断token是否为空还要判断onlineUserService里有没有该token。未登录的握手请求应该直接返回401状态码而不是放行后让消息处理逻辑抛空指针。做到这两点你的项目至少不会被老师一句话问穿。6. 让“源码数据库”的交付物更值钱的三个技巧这个项目到这里底层的运行逻辑基本就清楚了。接下来不讲基础运行只讲怎么让这套毕业设计从“能跑”变成“能讲出东西”。第一个技巧是给SQL脚本写清“数据字典”在脚本开头用注释列出每张表的作用、字段含义、以及和源码里哪个Mapper相对应。这样答辩时老师随机抽一张表你能立刻说出它的应用价值而不是对着表名现编。第二个技巧是给程序加一个“消息转发延迟”的观测点。在ChatWebSocketHandler里加上下面这种耗时统计long start System.nanoTime(); // 转发逻辑 long costMs (System.nanoTime() - start) / 1_000_000; if (costMs 50) { log.warn(message {} forward slow, cost {}ms, msg.getMsgId(), costMs); }这个简单的耗时统计会让你在答“系统性能如何”时有一个能拿出手的数字。配合/actuator/health做存活探活整个系统的可观测性就比普通毕业设计高出一截。第三个技巧是保留一份“环境依赖清单”JDK版本、Maven版本、MySQL版本、端口占用情况写在一个README.md里。这看起来和代码无关但却是压缩包交付物里最容易被老师认可“工程化意识”的地方。你可以尝试把群聊用conversation表扩展成group_member关联表也可以把消息表按时间分区但毕业设计的完成度主要取决于“链路内所有功能自洽”。按照从解压到跑通、再从跑通到讲清这条路走下来这个基于Spring Boot的桌面聊天室系统就不再只是标题里的一个项目而是你能随手改给别人用的一个完整案例。本文还有配套的精品资源点击获取
返回列表