Spring Boot校园二手交易系统:单体架构设计与实战经验分享

Spring Boot校园二手交易系统:单体架构设计与实战经验分享 简介本资源是一套基于Spring Boot开发的校园闲置物品交易系统完整源码面向Java后端初学者、高校课程设计学生及Web全栈学习者旨在解决校园内书籍、电子设备等闲置物品流通效率低、信息不对称的问题。压缩包共1565个文件33.16MB涵盖106个核心Java业务逻辑与控制器类、84个Vue前端组件如IndexHeader.vue、BreadCrumbs.vue等、306个JS交互脚本、88个CSS/SCSS样式文件、98个JPG/PNG图片及SVG图标资源以及MyBatis映射XML、Spring Boot配置YML、SQL建表语句等关键工程文件结构清晰、模块解耦便于理解MVC分层与前后端分离实践。已有90人下载学习可直接导入IDE运行完整包含用户认证、商品发布浏览、在线聊天协商、交易评价及后台管理等闭环功能附带Lombok简化、RESTful接口设计、密码哈希存储等工程化实践细节是掌握Spring Boot企业级开发流程的优质参考项目。1. 项目概述与核心价值最近在整理硬盘翻出来一个几年前在学校时做的项目源码包名字就叫“基于Spring Boot的校园闲置物品交易系统代码.zip”。当时为了毕业设计和积累实战经验吭哧吭哧搞了小半年从需求分析、技术选型到编码部署踩了不少坑也攒下不少心得。这个系统本质上是一个服务于校园内师生的C2C二手交易平台核心目标就是解决“宿舍里东西越堆越多毕业时一堆带不走又舍不得扔”的痛点。你想一本九成新的专业教材、一个只用过几次的健身器材、一张闲置的校园卡对原主人可能是负担但对学弟学妹可能就是宝贝。这个系统就是为这种高频、小额、基于信任的校内交易场景而生的。为什么用Spring Boot现在回头看这个选择依然非常正确。对于当时还是学生、开发资源和时间都有限的我们来说Spring Boot那种“开箱即用”的特性简直是救命稻草。它把Spring生态里那些繁琐的XML配置、依赖管理、服务器部署都简化了让我们能把精力集中在业务逻辑本身而不是在环境搭建上折腾一周。这个项目麻雀虽小五脏俱全涵盖了用户管理、商品发布、搜索浏览、在线聊天、订单交易、后台管理等完整模块是一个非常好的全栈实践样本。无论你是想学习Spring Boot如何在实际项目中落地还是想了解一个交易系统的核心架构设计甚至是打算自己动手做一个类似的校园应用这份代码和背后的思考过程或许都能给你一些直接的参考。2. 系统整体架构与核心设计思路2.1 为什么选择单体分层架构打开项目代码你会发现它采用的是经典的MVC分层架构并且是单体应用。这在当时是经过权衡的。很多同学可能会问现在不是都流行微服务吗为什么不用这里就涉及到技术选型的一个核心原则合适优于时髦。对于一个校园内的闲置物品交易系统它的典型特征包括用户规模有限通常在一所学校内几千到几万用户、业务逻辑相对直接、并发峰值可预测例如开学季、毕业季、开发和运维团队极小甚至就1-2个人。在这种情况下引入微服务带来的服务拆分、独立部署、分布式事务、链路监控等复杂度是得不偿失的。它会让开发、测试和部署的成本呈指数级上升。因此我们采用了单体分层架构清晰划分了职责控制层Controller接收前端HTTP请求进行参数校验和基本格式化然后调用服务层最后返回JSON数据。这一层要尽量薄不做业务逻辑。服务层Service这是业务逻辑的核心所在地。所有与交易、用户、商品相关的规则比如发布商品的条件、下单时的库存检查、聊天权限验证等都在这里实现。数据访问层Repository/Mapper负责与数据库对话使用MyBatis或Spring Data JPA来执行CRUD操作。这一层要保证高效和纯净只关心数据的存取。实体层Entity/Domain定义与数据库表映射的Java对象是数据流动的载体。这种架构的好处是结构清晰、开发简单、易于调试并且对于这个量级的应用性能完全足够。所有的代码都在一个工程里用Maven或Gradle就能构建扔到一台服务器上就能跑起来非常适合快速迭代和初期验证。2.2 技术栈选型背后的逻辑技术选型不是堆砌最火的技术而是为业务目标选择最合适的工具。这个项目的技术栈是这么考虑的核心框架Spring Boot 2.1.x选择2.1.x这个当时稳定的版本而非最新的快照版是为了避免踩坑。Spring Boot的自动配置、内嵌Tomcat、Starter依赖管理是基础。我们特别用到了它的spring-boot-starter-webWeb开发、spring-boot-starter-data-jpa数据访问、spring-boot-starter-security安全初期简单用后期可扩展和spring-boot-starter-websocket实现实时聊天。持久层Spring Data JPA Hibernate为什么用JPA而不是更灵活的MyBatis对于这个业务模型相对稳定、以对象操作为主的项目JPA的ORM对象关系映射能力可以极大减少编写重复的SQL代码。通过定义实体类如User,Product,Order和Repository接口就能完成大部分数据操作。OneToMany、ManyToOne这些注解能清晰地表达用户和商品、订单之间的关系。当然对于复杂的统计查询我们也会在Repository中使用Query注解写原生SQL兼顾灵活与便捷。数据库MySQL 5.7关系型数据库是交易类系统的基石ACID特性对于保证交易数据的一致性至关重要。MySQL成熟、稳定、社区资源丰富是毫无悬念的选择。我们设计了大概十几张表核心包括用户表、商品表、商品分类表、订单表、聊天消息表、收藏表等。前端技术Thymeleaf模板引擎 Bootstrap jQuery考虑到项目初期需要快速出原型并且团队成员后端背景更强我们没有采用前后端分离的Vue/React而是用了Spring Boot天然集成的Thymeleaf模板引擎来渲染页面。这样做的好处是开发速度快前后端耦合在同一个项目里调试方便。页面样式用Bootstrap框架能快速搭建出美观、响应式的界面。交互逻辑用jQuery补充。这虽然看起来“不现代”但对于一个MVP最小可行产品来说是最高效的方式。如果项目发展壮大完全可以重构为前后端分离。缓存与搜索初期未引入但预留了设计在最初的版本中考虑到校园用户量和商品量我们没有引入Redis或Elasticsearch。但我们在设计时做了预留。例如商品服务查询热门分类的方法上我们加了Cacheable注解的注释说明未来可以接入Spring Cache抽象层用Redis做后端缓存。对于商品搜索我们使用了数据库的LIKE查询这显然效率不高在代码注释中明确指出了这是性能瓶颈优化方案是接入Elasticsearch进行全文检索。注意技术选型文档化非常重要。我们在项目的README.md和一个专门的DESIGN.md文件里记录了为什么做这些选择以及未来可能的演进方向。这不仅是给队友看的更是给几个月后的自己看的。3. 核心业务模块拆解与实现细节3.1 用户系统不仅仅是注册登录用户模块是起点但绝不仅仅是/register和/login两个接口。实体设计Entity public class User { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String username; // 学号/工号唯一标识 private String password; // 加密存储 private String nickname; private String avatarUrl; // 头像存储OSS链接或相对路径 private String phone; private String email; private String dormitory; // 宿舍楼信息便于线下交易 Enumerated(EnumType.STRING) private UserRole role; // 枚举USER, ADMIN private Integer creditScore; // 信用分初始为100 private Boolean isActive; private LocalDateTime createTime; // 省略 getter/setter }这里有几个关键点1. 用学号/工号作为用户名保证了校内身份的真实性基础。2.password字段必须加密存储我们用的是Spring Security的BCryptPasswordEncoder。3. 引入了creditScore信用分这是构建信任体系的关键。信用分会根据交易完成情况、评价好坏而增减。注册流程的坑 注册时前端会发送验证码到用户邮箱校内邮箱验证是提升真实性的好方法。我们遇到过的一个典型问题是并发注册导致用户名重复。即使数据库对username字段设置了唯一索引在高并发下两个请求可能同时通过“用户名是否存在”的检查然后都去插入导致其中一个失败。我们的解决方案是在Service层方法上加Transactional注解并在检查用户名后立即执行插入利用数据库的事务隔离级别来防止这种情况。更严谨的做法是使用分布式锁但在这个场景下数据库唯一索引结合事务已足够。会话管理 我们没有采用传统的Session而是使用了JWTJSON Web Token。用户登录成功后服务端生成一个包含用户ID和角色的JWT令牌返回给前端。前端后续请求都在HTTP Header的Authorization中携带这个Token。这样做的好处是无状态便于扩展也适合未来可能的前后端分离部署。我们使用jjwt这个库来生成和解析Token。3.2 商品发布与管理系统这是系统的核心商品信息是否清晰、检索是否方便直接影响用户体验。商品实体与状态机Entity public class Product { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String title; Lob // 大文本字段 private String description; private BigDecimal price; private String mainImageUrl; // 主图 ElementCollection // 存储多张图片URL的集合 private ListString imageUrls; Enumerated(EnumType.STRING) private ProductStatus status; // 状态AUDITING审核中、ON_SALE出售中、SOLD已售出、OFF_SHELF已下架 Enumerated(EnumType.STRING) private ProductCondition condition; // 成色NEW, LIKE_NEW, GOOD, USED ManyToOne JoinColumn(name category_id) private Category category; // 关联分类 ManyToOne JoinColumn(name seller_id) private User seller; // 关联卖家 private Integer viewCount 0; private LocalDateTime createTime; private LocalDateTime updateTime; }状态设计是精髓ProductStatus这个枚举定义了商品的生命周期。一个新商品发布后状态是AUDITING审核中这是为了防止不良信息。管理员审核通过后变为ON_SALE。被下单后并非立即改变状态交易完成时变为SOLD。卖家也可以手动OFF_SHELF。这个状态流转必须严谨比如已售出的商品不能再被下单。图片上传的处理 这是文件操作的典型场景。我们没有把图片以二进制形式存到数据库而是使用了“对象存储数据库存路径”的方式。前端通过input typefile上传图片。后端FileUploadController接收到MultipartFile对象。我们对文件进行校验大小如5MB、类型仅限jpg, png、以及简单的病毒扫描调用简单的工具类。生成一个唯一的文件名如UUID 时间戳 后缀防止覆盖。将文件上传到阿里云OSS或腾讯云COS、七牛云等。在校园内网环境下如果追求极致速度和零成本也可以自建一个简单的文件服务器用Nginx做静态资源服务但OSS省心、可靠、有CDN加速。将上传成功后返回的公开访问URL如https://your-bucket.oss-cn-hangzhou.aliyuncs.com/images/uuid.jpg存入数据库的imageUrls字段。实操心得图片上传一定要做异步处理或至少提供进度提示。我们最初是同步上传大图片导致接口超时。后来改用了前端分片上传后端异步接收的方式用户体验好了很多。同时一定要在OSS上设置生命周期规则定期清理那些上传了但最终没有关联到任何商品的“僵尸图片”。3.3 实时聊天功能的实现二手交易沟通是关键。站内信太慢我们实现了基于WebSocket的实时一对一聊天。技术方案Spring WebSocket STOMP我们选择了STOMP作为WebSocket的子协议因为它提供了像消息目的地Destination、订阅Subscribe这样的高级抽象比裸的WebSocket更易用。后端配置Configuration EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { Override public void registerStompEndpoints(StompEndpointRegistry registry) { // 前端通过 ws://your-domain/ws 建立连接 registry.addEndpoint(/ws).setAllowedOriginPatterns(*).withSockJS(); } Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 客户端订阅消息的前缀例如 /topic, /user registry.enableSimpleBroker(/topic, /queue); // 客户端发送消息到服务端的前缀 registry.setApplicationDestinationPrefixes(/app); // 点对点消息前缀默认是 /user registry.setUserDestinationPrefix(/user); } }核心业务逻辑连接与认证前端建立WebSocket连接时需要携带JWT Token。我们通过实现HandshakeInterceptor接口在握手阶段验证Token并将用户信息存入Session。消息路由我们设计了两个主要的消息目的地。/app/chat.sendPrivateMessage用于接收前端发送的私聊消息。消息体包含recipientId接收者ID和content。/user/{userId}/queue/messages这是STOMP提供的用户专属队列。服务端可以通过SimpMessagingTemplate.convertAndSendToUser()方法将消息精准推送给指定用户。这样就实现了一对一聊天。消息持久化所有聊天消息都需要存入数据库的chat_message表包含发送者、接收者、内容、时间、是否已读等字段。这样用户离线再上线也能拉取历史记录。未读消息计数这是一个提升体验的关键点。当消息被接收并存储后如果接收者不在线即没有对应的WebSocket Session我们会在Redis或数据库里增加他的未读计数。当用户上线建立连接后主动查询未读计数并推送。当用户打开聊天窗口就将与对应卖家的未读消息标记为已读。踩过的坑连接断开与重连网络不稳定会导致连接断开。我们前端使用了SockJS客户端它支持多种传输方式降级并自带重连机制。后端需要处理好在SessionDisconnectEvent事件中清理用户会话信息。集群部署问题如果未来应用部署在多台服务器上简单的SimpMessagingTemplate无法跨服务器推送消息。解决方案是引入消息中间件如RabbitMQ、Redis Pub/Sub作为外部的消息代理Broker让所有服务器实例连接到同一个Broker。这就是为什么配置中用的是enableSimpleBroker内存代理未来可以换成enableStompBrokerRelay。3.4 交易与订单流程设计这是最需要保证数据一致性和安全性的部分。下单流程简化版前置校验用户点击“立即购买”或“加入购物车”我们系统是直接购买未做购物车。请求到达OrderController.createOrder。生成订单在Service层方法上添加Transactional注解开启事务。检查商品是否存在且状态为ON_SALE。检查卖家不是自己不能买自己的东西。可选检查库存对于闲置品我们通常设置库存为1。创建订单对象Order状态设为UNPAID待支付。订单号使用“时间戳随机数”生成确保唯一。将商品状态更新为RESERVED已预订或保持ON_SALE但关联订单ID。我们选择了后者避免引入过多状态。保存订单到数据库。支付由于是校园内部系统我们实现了一个简化的“模拟支付”。实际上只是调用一个支付接口改变订单状态为PAID已支付并记录支付时间。真实的支付需要接入支付宝或微信的当面付、小程序支付等。发货与确认卖家标记发货填写线下交易地点或快递单号订单状态变为SHIPPED已发货。买家确认收货后状态变为COMPLETED已完成。此时触发一系列后续操作商品状态正式变为SOLD。买卖双方互相评价。根据评价结果更新双方的creditScore信用分。事务与并发控制 在“创建订单”这一步存在“超卖”风险。即同一件商品被两个用户同时下单。我们通过数据库的乐观锁机制来解决。在商品实体中增加一个版本号字段version用Version注解。Entity public class Product { // ... 其他字段 Version private Integer version; }在更新商品状态或库存时SQL语句会带上where id? and version?。如果两个请求同时读到同一个version第一个更新成功后version会1第二个请求再去更新时匹配不到记录就会抛出ObjectOptimisticLockingFailureException。我们在Service层捕获这个异常然后返回给前端“商品信息已变化请刷新重试”的友好提示。重要提醒对于交易核心链路所有关键操作下单、支付、收货都必须有完整的日志记录。我们为Order表设计了详细的日志子表order_log记录状态变更的时间、操作人、原因。这在出现纠纷时是至关重要的证据。4. 后台管理系统的关键实现后台管理是平台有序运行的保障主要给管理员使用。我们单独做了一个管理后台通过/admin/**的URL路径进行拦截并通过用户角色UserRole.ADMIN进行权限控制。使用Spring Security进行权限控制Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers(/admin/**).hasRole(ADMIN) // 管理员路径需要ADMIN角色 .antMatchers(/user/**).hasAnyRole(USER, ADMIN) .antMatchers(/, /public/**).permitAll() .and() .formLogin() .loginPage(/login) // 自定义登录页 .permitAll() .and() .logout() .permitAll(); // 禁用CSRF以方便API测试生产环境应开启并妥善处理 http.csrf().disable(); } }核心管理功能商品审核管理员查看状态为AUDITING的商品列表可以查看详情然后进行“通过”或“拒绝”操作。拒绝时需要填写理由系统会通过站内信或WebSocket通知卖家。用户管理查看用户列表可以对违规用户进行“禁言”禁止发布商品或“封禁”禁止登录操作。操作同样需要记录理由。数据统计简单的数据看板展示每日新增用户数、商品发布数、成交订单数、热门商品分类等。这些数据通过编写复杂的SQL查询语句或使用JPA的Query注解从数据库聚合得到。如果数据量大这些统计查询应移到定时任务中计算好后存入统计表管理员查询时直接读统计表避免拖慢运营数据库。操作日志记录所有管理员的关键操作如审核商品、管理用户等做到有迹可循。后台的前端我们同样使用Thymeleaf渲染但套用了一个开源的AdminLTE后台模板快速搭建出了带有侧边栏、图表、表格的友好界面。5. 系统部署、监控与性能考量5.1 从开发到上线的部署流程项目开发完成后如何让同学们访问我们经历了从本地跑到服务器部署的过程。打包使用Spring Boot Maven插件打成可执行的JAR包。mvn clean package -DskipTests生成的target/*.jar文件包含了所有依赖和嵌入的Tomcat服务器。服务器环境我们申请了一台学校机房的Linux服务器CentOS 7。基础环境包括JDK 8或11与本地开发环境一致。MySQL数据库并创建了对应的数据库和用户导入初始数据如商品分类。可选Redis如果未来要加缓存或做Session共享。部署脚本我们写了一个简单的Shell脚本deploy.sh来做自动化部署。#!/bin/bash APP_NAMEsecondhand-trade JAR_PATH/path/to/your/project/target/*.jar BACKUP_PATH/path/to/backup/ LOG_PATH/path/to/logs/ # 1. 停止旧进程 echo Stopping old application... pkill -f $APP_NAME || true sleep 5 # 2. 备份旧JAR包可选 cp $JAR_PATH $BACKUP_PATH/$APP_NAME-$(date %Y%m%d%H%M%S).jar # 3. 启动新应用 echo Starting new application... nohup java -jar $JAR_PATH --spring.profiles.activeprod $LOG_PATH/app.log 21 echo Application started. PID: $!这个脚本做了进程停止、备份和启动。更成熟的做法是使用Docker容器化部署用Docker Compose管理应用、MySQL、Redis等服务实现环境隔离和一键启动。配置分离我们使用Spring Boot的application-{profile}.properties功能。开发时用application-dev.properties连接本地数据库生产环境用application-prod.properties连接服务器数据库配置更严格的日志级别等。通过--spring.profiles.activeprod启动参数来激活生产配置。5.2 基础监控与日志排查系统跑起来后怎么知道它是否健康健康检查Spring Boot Actuator是神器。我们引入了spring-boot-starter-actuator依赖并做了一些安全配置只暴露必要的端点。这样我们就可以通过访问/actuator/health来查看应用健康状态数据库连接等通过/actuator/metrics查看一些基础指标。日志管理我们使用Logback作为日志框架在logback-spring.xml中配置了日志格式、输出级别生产环境用INFO问题排查时临时改为DEBUG以及按天和大小滚动归档的策略避免日志文件撑满磁盘。appender nameFILE classch.qos.logback.core.rolling.RollingFileAppender file${LOG_PATH}/app.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePattern${LOG_PATH}/app.%d{yyyy-MM-dd}.%i.log/fileNamePattern maxHistory30/maxHistory timeBasedFileNamingAndTriggeringPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedFNATP maxFileSize100MB/maxFileSize /timeBasedFileNamingAndTriggeringPolicy /rollingPolicy encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n/pattern /encoder /appender关键日志点我们在所有Controller的入口、Service层的重要方法尤其是交易相关、异常捕获处都打印了日志包含用户ID、操作类型、关键参数等信息。这样当用户反馈“下单失败了”我们可以根据他的用户ID和时间点快速定位到相关日志进行排查。5.3 性能优化点分析虽然初期数据量不大但我们在设计时考虑了一些常见的性能瓶颈和优化方向数据库层面索引在Product表的category_id,status,create_time用于按时间排序等查询频繁的字段上建立了索引。在Order表的buyer_id,seller_id,status上也建立了索引。慢查询监控开启MySQL的慢查询日志定期分析对执行时间过长的SQL进行优化比如重构查询、增加索引。连接池使用HikariCP作为数据库连接池这是Spring Boot 2.x的默认选择性能非常好。我们在application-prod.properties中调整了连接池参数如最大连接数、最小空闲连接、连接超时时间等以适应生产环境的并发需求。应用层面DTO与懒加载在查询商品列表时我们不会返回完整的Product实体它关联了卖家User对象User又有其他关联...而是定义了一个ProductSummaryDTO只包含列表页需要的字段标题、价格、主图等。这避免了Hibernate的N1查询问题。对于必须加载的关联对象在Repository查询方法上使用EntityGraph或写JOIN FETCH的JPQL来一次性加载避免多次查询。缓存如前所述热门分类、用户基本信息等变化不频繁的数据是引入Redis缓存的首要目标。使用Spring Cache抽象可以轻松地通过注解如Cacheable,CacheEvict来管理缓存。前端层面图片懒加载与压缩商品列表图片很多我们让前端实现图片懒加载当图片进入视口时才加载。同时要求用户上传的图片在后端或OSS处理时进行适当压缩减少传输体积。静态资源CDN将CSS、JS、图片等静态资源放到CDN上加速访问。6. 常见问题排查与实战技巧在实际开发和运行中我们遇到了各种各样的问题这里总结几个典型的6.1 数据库连接超时或中断现象应用运行一段时间后控制台偶尔会报出Communications link failure或Connection is not available的错误。排查与解决检查连接池配置可能是连接池的最大空闲时间idleTimeout设置小于数据库的wait_timeout。当连接空闲时间超过数据库的wait_timeout数据库会主动断开连接而连接池并不知道再次使用这个失效连接就会报错。确保HikariCP的idleTimeout略小于MySQL的wait_timeout。检查网络与防火墙服务器和数据库服务器之间的网络是否稳定防火墙是否会在长时间无通信后断开连接启用连接保活在HikariCP配置中可以设置connectionTestQuery如SELECT 1和keepaliveTime让连接池定期测试连接的有效性。6.2 事务失效问题现象在Service方法里抛出了异常但数据还是被部分保存了事务没有回滚。原因与解决异常类型不对Spring事务默认只对RuntimeException和Error进行回滚。如果你在方法里抛出了一个Exception事务是不会回滚的。需要在Transactional注解里明确指定rollbackFor Exception.class。方法访问权限Transactional是基于AOP代理实现的如果方法不是public代理可能无法生效。自调用问题在同一个类中一个没有Transactional注解的方法A调用了同一个类中有Transactional注解的方法B事务是不会生效的。因为代理对象调用方法B时走的是类内部调用而不是通过代理。解决方法是把方法B放到另一个Service中或者使用AopContext.currentProxy()来获取当前类的代理对象再调用。6.3 图片上传失败或访问慢现象用户上传图片时前端长时间等待然后报错或者图片上传成功了但页面加载非常慢。排查文件大小限制检查Spring Boot的multipart.maxFileSize和maxRequestSize配置确保它们大于用户上传的图片大小。OSS配置与网络检查OSS的Bucket权限是否为公共读如果是存储公开图片。检查服务器到OSS外网域名的网络是否通畅。可以尝试在服务器上用curl或wget测试上传和下载速度。前端超时设置增加前端上传组件的超时时间。CDN加速如果图片访问慢可以为OSS Bucket开启CDN加速这样图片会被缓存到离用户更近的节点。6.4 实时聊天消息丢失或重复现象用户反映有时收不到消息或者同一条消息收到了两次。排查消息持久化与发送的原子性确保“消息存入数据库”和“通过WebSocket发送”这两个操作在一个事务里。如果先发送后存储发送成功后系统崩溃消息就丢了。如果先存储后发送存储成功后发送前崩溃消息状态会不一致。我们采用的方式是先存储状态为“发送中”然后尝试发送发送成功后更新消息状态为“已发送”如果发送失败捕获异常则记录日志并可能加入重试队列。客户端消息去重为每条消息生成一个唯一的ID如UUID前端在收到消息后检查本地是否已存在该ID如果存在则丢弃。这可以解决网络延迟导致的消息重复接收问题。离线消息处理用户离线时消息应持久化到数据库。当用户上线后需要主动拉取所有未读消息。这个“拉取”的时机很重要应该在WebSocket连接成功建立、并且用户身份认证完成后立即进行。这个校园闲置物品交易系统的项目虽然从技术上看用的都是主流、成熟的框架组合但真正把它从零到一跑通并且能稳定支撑几百上千人的日常使用其中涉及的细节和思考远比单纯学习框架API要复杂。它考验的是你将技术组合起来解决实际问题的能力包括需求分析、架构设计、数据库建模、业务逻辑编码、异常处理、性能调优乃至简单的运维部署。回过头看这段经历最大的收获不是学会了某个注解怎么用而是建立了一套完整的、从问题到代码的工程化思维。如果你正在学习Spring Boot强烈建议不要只停留在跑通Demo试着用它去构思并实现一个能解决你身边实际问题的完整应用这个过程会让你对技术的理解深入一个层次。代码就在那里关键是你如何去理解和改造它让它为你所用。本文还有配套的精品资源点击获取