每年到这个时间点,总有一大批计算机专业的同学开始焦头烂额地找毕业设计题目。你打开搜索框输入“计算机毕业设计”,跳出来的结果要么是看着高大上但根本跑不起来的论文项目,要么是已经烂大街到答辩老师看一眼就皱眉的图书管理系统。今天我想好好聊聊的,是基于Spring Boot的闲置物品交易平台,项目编号03655。这个题目我前前后后带过不少学生做过,也自己完整地把整个流程从零到一跑通过,算是有比较深的体会。如果你正在纠结选题,或者已经选了类似方向但不知道从哪里下手,这篇文章应该能帮你省下不少时间。
先说说这个项目能带给你什么。它是一个典型的Web全栈实战项目,核心是围绕“用户发布闲置商品→买家浏览下单→双方完成交易”这条完整链路展开。你不仅能练到Spring Boot的后端接口开发、MyBatis Plus的数据库操作、Redis缓存、JWT登录鉴权这些高频技术点,还能把手上的Vue或Thymeleaf前端技能一并串起来。最重要的是,这个业务场景足够生活化,给你做功能设计和答辩讲解都提供了非常自然的切入点。
我打算从题目拆解、技术选型、数据库设计、核心逻辑实现、部署演示到最后的问题排查,完整地把这个项目从头到尾过一遍。你可以把它当成一份“带注释的源码导读”,也可以当成一套“毕业设计从立项到答辩的操作手册”。不管你是刚接触Spring Boot的新手,还是已经有基础的进阶选手,这篇文章的内容都足够你直接拿来用。
1. 项目整体设计与思路拆解
1.1 题目到底想让你做什么
先别急着写代码,把题目翻译成人话。闲置物品交易平台,本质上就是一个C2C模式的二手交易系统。和淘宝、转转这类商业产品相比,作为毕业设计,你不需要做复杂的推荐算法、支付网关、物流追踪——那些是加分项但不是必需品。你真正需要交付的是一个能跑通完整业务闭环的系统:用户能注册登录,能发布商品,能看到商品列表,能下单,然后有一个简单的订单状态管理。
但这里有个很多人会踩的坑:功能跟着感觉走,想到哪做到哪。今天加一个积分系统,明天加一个秒杀活动,最后数据库表建了二三十张,代码写了一万多行,结果自己都说不清楚业务主线是什么。我给你的建议是,先把核心链路画出来:用户→发布闲置→商品上架→浏览搜索→下单→订单管理→交易完成。所有功能都围绕这条链路去扩展,宁可做得少而精,也不要做成一个四不像。
从源码编号03655提供给我们的信息来看,这个项目的核心模块大致包括:
- 用户模块:注册、登录、个人信息维护、地址管理
- 商品模块:发布闲置、商品分类、商品搜索、商品详情
- 交易模块:下单购买、订单列表、订单状态流转、取消订单
- 互动模块:收藏商品、留言咨询(这个属于加分项,建议做上)
- 管理后台:用户管理、商品审核、分类管理、数据统计
1.2 为什么把技术栈定为Spring Boot全家桶
现在很多同学问我,Java后端那么多框架,SSH、SSM、Spring Boot到底选哪个?我的回答一直很明确:选Spring Boot,没有悬念。
一方面是因为现在的企业开发早已全面转向Spring Boot,你写进简历里的项目如果还在用SSH,面试官大概率会问你是不是从上古时代穿越过来的。另一方面,Spring Boot的自动配置机制帮你省掉了大量繁琐的XML配置,这对毕业设计这种时间紧、任务重的场景简直太友好了。你可能只需要在pom.xml里引入一个依赖,加上几行application.yml配置,就能快速启动一个Web项目。
我见过有同学固执地用SSM框架手写配置,结果光Spring和MyBatis的整合就折腾了快一周,最后连登录功能都没做完。不是说SSM不能做,而是这个时间成本花得太不值了。毕业设计的核心目标是在有限时间内交付一个功能完整、逻辑清晰、答辩说得清楚的项目,Spring Boot+MyBatis Plus+Vue的前后端分离方案,是目前公认效率最高、容错率也最高的组合。
1.3 角色划分与业务流转
在正式设计数据库之前,先把系统的角色和权限边界理清楚。这个平台至少有三类使用者:
第一类是普通用户,也就是卖家兼买家。用户登录后可以发布闲置商品、管理自己发布的商品、下订单买别人的东西、处理自己收到的订单。第二类是系统管理员,负责对用户、商品、分类、订单进行后台管理,尤其需要对发布上架的闲置物品做审核,防止出现违规商品。第三类是游客,也就是未登录的访客,可以浏览商品列表和详情,但需要登录后才能下单和发布。
从业务流转上看,一个典型的使用场景是这样的:毕业生小王有一台闲置的iPad,他注册登录后发布了一条商品信息,填好标题、描述、价格、成色,上传两张实物图,点击发布。管理员在后台审核通过后,商品出现在广场列表里。另一个同学小李在搜索框输入“iPad”,看到了这个商品,点进详情页后觉得价格合适,直接下单付款(这里一般用模拟支付)。小王在“我卖出的”订单列表里看到新订单,联系小李约定线下交易。交易完成后,小王点击发货,小李确认收货,订单状态变为已完成,整个闭环就结束了。
你把这个故事讲清楚,答辩的时候老师基本就不会在业务流程上难为你。接下来的所有模块设计,都是为了让这个故事能够流畅地跑起来。
2. 核心功能模块的详细拆解与技术实现
2.1 从登录鉴权开始搭建安全防线
登录注册是每个系统都绕不开的基础模块,但越是基础的地方越容易出问题。很多毕业设计项目被老师问倒,都是倒在“用户密码怎么存的?”这个问题上。如果你回答“明文存储在数据库里”,那基本等于告诉老师你的安全意识是零。
正确的做法是使用哈希加密。在Spring Boot项目里,我习惯使用BCrypt算法来处理密码,Spring Security框架里自带BCryptPasswordEncoder,如果你没有引入Spring Security,单独引入spring-security-crypto这个包也能用。核心逻辑非常简单:用户注册的时候,对原始密码做加密再入库;用户登录的时候,对输入的密码做校验。这样即使数据库泄露了,攻击者拿到的也是一堆没办法反推原文的哈希值。
登录成功之后,前后端分离的项目一般用Token来维持会话状态。最常用的方案是JWT,把用户ID、角色等关键信息放进Token里,后端在拦截器里统一校验Token的合法性和有效性。这里给你一个我在实践中的配置思路:
jwt: secret: your-secret-key-change-in-production expire: 86400 # 单位:秒,24小时过期然后在代码里写一个JwtUtil工具类,负责生成Token和解析Token。再配合一个WebMvcConfigurer去注册登录拦截器,把需要鉴权的接口路径保护起来。比如“/api/user/”、“/api/order/”这些接口,必须是登录状态才能访问;而商品列表、商品详情这类查询接口,游客也能看,所以不要拦。
这里有个很容易犯的错误:拦截器把所有接口都拦截了,结果前端在没登录的情况下访问首页都拿不到数据。所以路径放行的配置一定要仔细,通常把/api/user/login、/api/user/register、/api/goods/list、/api/goods/detail/**这些接口放到白名单里。
2.2 商品发布模块:每个字段都值得想清楚
商品发布是整个平台最核心的内容生产入口,这个环节直接决定了数据库表结构的设计,也直接影响后续商品检索、详情展示的实现难度。
发布闲置的表单字段,我是这样设计的:
- 商品标题:限制在20-50个字符,太短了识别度低,太长了列表页排版很难看
- 商品描述:500字以内,支持换行和表情符号,前端做多行文本输入
- 商品分类:使用三级分类,比如“数码产品→平板电脑→iPad”,不过作为毕业设计做到一级或二级就够用了
- 商品价格:用decimal类型,精度控制在两位小数,前端用InputNumber组件限制输入格式
- 商品成色:用一个字典类型,比如“全新、几乎全新、轻微使用痕迹、明显使用痕迹、有维修史”
- 交易方式:同城面交、邮寄、两者皆可
- 商品图片:支持多图上传,最多9张,首图作为封面图
图片上传这个功能,看起来容易做起来坑不少。如果只是简单地保存到本地磁盘,部署之后换个环境图片就全丢了,而且服务器重启之后访问路径经常会出问题。要省心的话,你可以用本地存储把图片放在一个固定的目录下,然后配置一个静态资源映射,让前端可以通过URL直接访问。我在项目中常用的做法是用MinIO或阿里云OSS,但考虑到毕业设计可能没有云资源,用本地存储或者在Linux服务器上装一个MinIO都是比较稳的选择。
图片上传接口的设计上,我建议前端先调用/api/upload/image接口把图片传上去,拿到返回的URL,再把URL作为字符串提交到商品发布接口。这样做的优势是商品提交和文件上传解耦,即使商品最终没有发布成功,也不会产生冗余的文件垃圾。
2.3 商品检索:从SQL到Elasticsearch都要心里有数
商品搜索功能是一个很好的加分点。最基础的方案就是模糊查询,用MyBatis Plus的like条件就能实现:
LambdaQueryWrapper<Goods> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.isNotBlank(keyword), Goods::getTitle, keyword) .eq(Goods::getStatus, 1) // 只查已上架的商品 .orderByDesc(Goods::getCreateTime);这个方案对数据量几百上千条的毕业设计场景完全够用。但如果答辩的时候你想展示一些更高级的玩法,可以在项目里集成Elasticsearch做全文检索,把商品标题和描述建立索引,然后用matchQuery去实现搜索。这样不仅能搜标题,还能搜描述内容,排序和分词效果也更好。不过我要提醒你一句:如果你的机器配置一般,不建议强上ES,光一个Elasticsearch进程就能吃掉不少内存,倒是可以考虑MySQL的全文索引作为折中。
除了关键词搜索,分类筛选和价格区间筛选也是用户经常使用的功能。我建议你在商品列表页的侧边栏提供“分类树+价格区间+成色筛选”的组合条件,后端用条件构造器动态拼接查询条件,这样代码不臃肿,维护起来也方便。
2.4 订单交易与状态机设计
订单模块是整个系统中业务逻辑最复杂的地方,也是最容易出Bug的地方。核心问题是:订单状态如何流转?谁可以改变状态?
我在项目中设计了这样一个状态流转链条:
待付款 → 待发货 → 待收货 → 已完成 ↘ ↘ ↘ 已取消 已取消 已取消用户提交订单后,订单状态变为待付款(或直接跳过支付环节变为待发货,视你的模拟支付策略而定)。卖家看到待发货订单,确认后点击发货,状态变为待收货。买家收到货后确认收货,状态变为已完成。在待付款和待发货阶段,用户都可以申请取消订单。
这个状态机建议你在数据库里用一个order_status字段来标识,同时建一张order_status_log表记录状态变更日志。比如状态从2变成3,日志表里新增一条记录,记录操作人、操作时间、从哪个状态变成哪个状态。状态日志的作用不只是答辩时能展示你的设计严谨,更重要的是当订单出问题时,你能快速回溯问题出在哪个环节。
实现方式上,我在代码里用一个OrderStatusEnum枚举类来定义所有状态,服务层用switch-case或者if-else来校验状态转移是否合法。比如用户想取消一个“已完成”的订单,那就必须抛异常阻止。这种做法在小型项目中简单直接,不必为了追求设计模式而引入复杂的状态机框架(比如Spring StateMachine)。
2.5 后台管理模块:用得上也要做得出来
挂在用户中心之外,后台管理模块是展示你“完整项目能力”的关键。很多同学做了用户端就以为万事大吉,结果答辩老师一问“你这个系统谁负责管理?”,瞬间就愣住。一个完整的闲置交易平台,后台管理至少需要以下功能:
- 用户管理:查看所有注册用户,重置密码、禁用账号、查看用户发布的商品列表
- 商品管理:审核用户发布的闲置商品,可以对违规商品进行下架处理
- 分类管理:维护商品分类树,增删改查
- 订单管理:查看全部订单,支持按订单号、手机号、买家昵称搜索
- 数据看板:展示总用户数、总商品数、今日新增订单量、交易成功率等统计数字
数据看板这个功能看上去复杂,其实做起来不难。它本质上就是几个聚合查询函数:
long userCount = userService.count(); long goodsCount = goodsService.count(); long todayOrderCount = orderService.count(new LambdaQueryWrapper<Order>() .ge(Order::getCreateTime, DateUtil.beginOfDay(new Date())));把这些数据聚合到一个DashboardVO对象里,前端用ECharts画几个统计图表,就足够撑起场面了。如果你时间充裕,还可以加上近七天的订单趋势折线图、分类占比饼图,展示效果会更加分。
3. 数据库设计与核心表结构剖析
3.1 数据库建模的原则
数据库是一个项目的基石,也是答辩时老师重点关注的部分。表设计得乱七〔八糟,即使功能都能跑通,老师也会觉得你的基本功不扎实。我建议你在动手写代码之前,画一张ER图,把实体之间的关系先理清楚。
对于闲置物品交易平台,核心实体至少有:用户、商品、订单、订单项、收藏、商品分类、轮播图。实体之间的关系是:用户与商品是1对N,用户与订单是1对N,订单与订单项是1对N,商品与分类是N对1,用户与收藏是N对N(通过收藏表关联)。
3.2 核心表字段参考
下面我给出几张核心表的字段设计,你可以直接参考,也可以根据自己的功能扩展做调整。
用户表(t_user):
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,雪花算法或自增 |
| username | varchar(50) | 用户名,唯一 |
| password | varchar(100) | BCrypt加密后的密码 |
| nickname | varchar(50) | 昵称 |
| avatar | varchar(255) | 头像URL |
| phone | varchar(20) | 手机号 |
| varchar(100) | 邮箱 | |
| status | tinyint | 状态:0禁用,1正常 |
| create_time | datetime | 注册时间 |
| update_time | datetime | 更新时间 |
商品表(t_goods):
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| user_id | bigint | 发布者ID |
| category_id | bigint | 分类ID |
| title | varchar(100) | 商品标题 |
| description | text | 商品描述 |
| price | decimal(10,2) | 价格 |
| original_price | decimal(10,2) | 原价,用于展示折扣 |
| condition_level | tinyint | 成色编码 |
| images | varchar(1000) | 图片URL,逗号分隔 |
| status | tinyint | 0待审核,1已上架,2已下架,3已售出 |
| view_count | int | 浏览数 |
| create_time | datetime | 发布时间 |
订单表(t_order):
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| order_no | varchar(32) | 订单编号,唯一 |
| buyer_id | bigint | 买家ID |
| seller_id | bigint | 卖家ID |
| goods_id | bigint | 商品ID |
| total_amount | decimal(10,2) | 订单金额 |
| status | tinyint | 状态:0待付款,1待发货,2待收货,3已完成,4已取消 |
| buyer_message | varchar(255) | 买家留言 |
| create_time | datetime | 下单时间 |
| pay_time | datetime | 支付时间 |
| ship_time | datetime | 发货时间 |
| finish_time | datetime | 完成时间 |
这里有一个设计细节可以体现你的专业度:下单时不要直接修改商品表里的status字段,而是通过订单状态来驱动商品状态的变化。比如商品被下单后,商品状态还是“上架中”,等买家支付成功或订单完成后再把商品状态改为“已售出”。这样做的好处是如果订单取消,商品可以快速恢复为可售状态,不需要额外写恢复逻辑。
3.3 索引优化与常用SQL
数据库表建好后,别忘了加索引。在数据量不大时索引的效果看不出来,但只要数据量上到几万条,没有索引的模糊查询会让你等到怀疑人生。我在这个项目里固定加索引的字段有:
- 订单表的order_no(唯一索引),有时候需要按订单号精确查单
- 商品表的user_id,查询“我发布的商品”时高频使用
- 商品表的category_id,分类筛选时高频使用
- 订单表的buyer_id和seller_id,查询买家订单和卖家订单时高频使用
这里给你一个简单的SQL优化思路。比如“查询用户发布的商品列表”,如果只是按照user_id去t_goods表里扫,走主键索引肯定没问题。但如果你还想过滤status字段,就需要一个联合索引(user_id, status)来减少回表的成本。在navicat或者DataGrip里用EXPLAIN执行一下,你自己就能看到走了什么索引。
4. 前后端联调与关键接口设计
4.1 统一接口返回结构
前后端分离的项目,接口设计是否规范直接决定联调的效率。很多同学传来的接口一会儿返回{code:200,data:...},一会儿返回{success:true,message:...},前端处理起来苦不堪言。
我建议你从一开始就定义一个统一的返回结构,在代码里写一个Result<T>类:
public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }所有Controller的返回类型都统一用Result<T>包裹,前端拿到响应后先判断code是否为200,再做后续逻辑处理。这样做不仅能减少沟通成本,而且配合全局异常处理器,后端报错也能统一以规范的JSON格式返回给前端,而不是抛出一堆堆栈信息。
4.2 Swagger接口文档自动生成
另一个能大大提升开发效率的工具是Swagger(在Spring Boot 3里通常用springdoc-openapi)。配置好之后,访问/swagger-ui.html就能看到所有接口的在线文档,前端同学可以自己查看参数、测试接口,不用再来问你某个接口该传什么参数了。
在pom.xml中引入依赖后,只需要在Controller类上添加@Tag注解,在接口方法上添加@Operation注解,Swagger就能自动扫描并生成文档。你还可以在全局配置类里设置一些基础信息,比如项目名称、版本号、联系人等。这篇博文就不放完整代码了,项目源码里都有,你可以直接去看。
4.3 分页查询的标准化处理
列表页的分页也是高频功能。MyBatis Plus提供了非常好用的分页插件,只需要配置一下MybatisPlusInterceptor:
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }之后在Service层直接调用page(new Page<>(pageNum, pageSize), wrapper)就能拿到分页结果。给前端返回的数据结构中,我会把总记录数、当前页、每页大小、总页数和数据列表都放进去,前端只要照着这个结构渲染分页组件就行了。
这里提醒你一下:分页插件不生效是高频问题,十有八九是配置类没被Spring扫描到,或者引入的版本和你当前的MyBatis Plus版本不兼容。如果发现分页不起作用,先把@Configuration注解和包扫描路径检查一遍,八成能解决。
4.4 跨域问题与CORS配置
前后端分离之后,你可不能不配跨域。前端运行在http://localhost:8080,后端运行在http://localhost:9090,这两个端口不同,天然是跨域,浏览器默认会拦截请求。解决方式有很多,JSONP、Nginx反向代理、CORS跨域头,最简单直接的是在后端写一个CORS配置类:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }这里注意一个细节:如果开启了allowCredentials(true),那么allowedOriginPatterns不能写成*,否则部分浏览器会报错。这是我踩过的一个很隐蔽的坑,写在这里希望能帮大家避开。
5. 部署演示与答辩准备
5.1 本机快速启动与演示环境搭建
毕业设计到了最后阶段,能不能流畅地给老师演示,直接影响最终成绩。我给你的建议是,准备一套独立的演示环境,不要在答辩现场临时启动开发环境。本地电脑上光是Idea启动项目就要一分钟多,加上前端npm run dev又得等几十秒,一旦出问题,现场气氛会非常尴尬。
我这里整理了一个稳健的本机部署流程:
- 安装MySQL 8.0,创建数据库,执行项目里提供的init.sql脚本,导入表结构和初始数据
- 安装Redis,Windows就用Windows版,Mac或Linux就用Docker或brew安装,启动服务
- 修改后端的application.yml,把数据库账号密码、Redis地址改为本机配置
- 用Idea打开后端项目,等待Maven下载依赖,启动Spring Boot应用
- 打开前端项目,运行npm install安装依赖,npm run dev启动前端服务
- 浏览器访问前端地址,用预置的测试账号登录(建议在答辩前把所有角色都准备好:普通用户、管理员)
我在源码提供的数据脚本里,已经预置了一个管理员账号admin/admin123和一个普通用户test/test123,每次演示前一定要先验证一下这两个账号能正常登录,不然现场翻车就麻烦了。
5.2 演示脚本怎么准备
有同学问我要不要准备演示脚本,我的回答是:必须准备。答辩演示的时间通常只有五到十分钟,你不可能把所有功能都点一遍。这时候要挑最有代表性的主链路来演示,节奏控制在:
- 浏览商品广场,展示分页、筛选、搜索
- 注册一个新账号(或者直接用测试账号登录)
- 发布一件闲置商品,演示图片上传
- 切换账号,找到刚发布的商品,下单
- 回到卖家账号,处理订单,发货
- 买家确认收货,订单完成
- 进入后台管理,审核一个待审核商品
这条线走完,几乎覆盖了平台的所有核心模块。老师如果中间问起某个功能,你再说“这个功能在XX模块,我可以演示给您看一下”,然后快速切过去。这样整个流程都在你的掌控之中,不容易被问懵。
5.3 答辩常见问题提前准备
答辩环节,老师一般会围绕设计思路、技术选型、遇到的困难、未来的改进方向来提问。提前准备好答案,心里就有底了。我按经验整理了高频问题:
- 为什么选择Spring Boot?答:自动配置简化开发、生态完善、企业主流。
- 密码为什么用BCrypt?答:哈希加盐不可逆,即使数据库泄露也无法还原明文。
- 商品搜索怎么做的?答:基础版MySQL模糊查询,进阶版引入Elasticsearch做全文检索。
- 订单状态怎么管理?答:状态机+状态日志表,每一步状态变更都有记录。
- 缓存用了Redis的哪些数据结构?答:商品详情缓存用String,热门搜索词用ZSet,登录Token也可以存Redis。
- 项目有什么不足?将来怎么改进?答:支付环节目前是模拟的,后续可以接入微信支付或支付宝沙箱;推荐算法比较粗糙,后面可以引入协同过滤。
我发现很多同学被老师问到“有什么不足”时,总喜欢说自己项目“没有不足”,这就等于自己把退路堵死了。正确的说法是:先坦诚项目在某方面的局限性,再给出可行的优化思路。比如模拟支付只是调用了一个本地接口,没有对接真实支付渠道,但已经想好了支付网关的对接方案。这样既显得诚实,又展示了学习能力。
6. 常见问题与排查技巧实录
6.1 Spring Boot版本太高导致的启动报错
我最近遇到好几个来自“springboot版本太高”热搜的学生来问,说项目的Spring Boot版本升级之后,应用启动就报Failed to configure a DataSource,或者有些旧依赖怎么都引入不进来。这个问题的根源,通常是Spring Boot 3.x相对于2.x做了比较大的调整,比如javax包改成了jakarta包,一些老的第三方库没有及时适配。
如果你拿到的毕业设计源码是基于Spring Boot 2.x写的,我建议不要轻易升级到3.x。版本能跑通就尽量锁定,让Maven仓库里的版本和pom.xml保持一致。确实需要升级的话,除了改依赖版本,还要重点检查:javax是否全部替换为jakarta、Redis连接工厂的配置方式是否变化、MyBatis Plus的分页插件是否兼容。
6.2 图片上传成功但无法访问
图片上传到本地磁盘后,浏览器访问404,这个问题在前后端分离项目中太常见了。原因是Spring Boot默认只处理classpath下的静态资源,你上传到服务器磁盘的文件路径并不在静态资源映射范围内。
解决方案是在配置类中添加资源映射:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadPath); } }同时建议在application.yml里把上传目录做成可配置项,比如:
upload: path: ./upload-dir这样换部署环境时只要改配置就能生效,不用重新编译代码。
6.3 Redis连接失败导致登录接口504
有些同学的项目把一部分数据缓存放到了Redis里,登录的时候要先往Redis里写Token。如果Redis没有启动,或者Redis的ip端口填错了,接口就会一直超时。排查时先确认Redis进程是否启动了,再用redis-cli ping测试一下能返回PONG,基本就能定位是不是连接问题。
还有一个细节:不要把Redis密码硬编码在代码里。配置到application.yml里,通过@ConfigurationProperties读取。我之前接过一个项目,密码里带了@等特殊字符,YAML解析直接报错,后来加上了双引号才解决。
6.4 分页插件不生效
分页查出来仍然是全量数据,这是MyBatis Plus的一个经典坑。一般是两个原因:一是没有把分页插件注册到IOC容器,二是扫描包路径不对导致拦截器没有生效。解决方式很简单,把配置类检查一遍,确保@Configuration下的MybatisPlusInterceptor注入成功,而且interceptor的position要放在第一个,避免和其他拦截器冲突。调试时可以在日志里观察打印的SQL,如果SQL里面有LIMIT关键字,就说明分页已经生效了。
6.5 数据库连接过多导致服务不可用
开发过程中频繁重启项目,容易导致MySQL连接数满,报Too many connections。这个大多是因为连接池配置不当或应用没有释放连接。在application.yml里用HikariCP(Spring Boot 2.x之后默认的连接池)可以这样配置:
spring: datasource: hikari: maximum-pool-size: 10 minimum-idle: 5 connection-timeout: 30000把最大连接数控制在一个合理的范围内,能有效避免开发环境下连接泄漏拖垮数据库。
7. 代码逻辑层面的重要细节
7.1 事务控制
一个业务逻辑涉及多张表更新的时候,一定要记得加@Transactional注解。比如下单操作,不仅要往订单表插入一条记录,还要更新商品的状态。如果这两个操作一个成功一个失败,数据就不一致了。Spring的声明式事务只需要加一个注解,非常方便。
但要注意,@Transactional只对RuntimeException和Error回滚,对受检异常默认是不回滚的。如果你在方法里捕获了异常而没有重新抛出,事务也会失效。这是面试常考的一类细节问题,在项目里最好能用TransactionTemplate做一次编程式事务的演示,答辩时提到这个会是一大亮点。
7.2 全局异常处理
一个健壮的项目必须有一个全局异常处理器。不然一旦代码里抛出了空指针,前端收到的是500错误和一些堆栈信息,既不友好也不安全。有了@RestControllerAdvice,你就可以自定义返回统一的JSON错误结构,同时把详细的异常信息写到日志文件里,方便排查。
我在这个项目里把自定义的BusinessException和系统的Exception分开处理。业务异常(比如“商品不存在”“订单状态不允许取消”)直接返回对应提示,系统异常记录日志并返回友好提示“系统繁忙,请稍后再试”。这个设计很符合企业开发规范,也是答辩时一个不错的展示点。
7.3 参数校验
前端表单校验固然重要,但后端的参数校验不能省。Spring Boot里你可以用@Validated和@NotBlank、@Size、@Email等注解,在接口入参实体上加校验规则,校验失败时会自动抛出MethodArgumentNotValidException,再由全局异常处理器统一处理。我见过有的项目只在前端做校验,后端接口谁都能直接调,传一个空字符串的商品标题也能入库,这属于埋雷行为。后端参数校验是底线,必须做。
8. 从毕业设计到工程能力的进阶建议
做完这个项目,你的Spring Boot基础应该算是打牢了。但毕业设计只是一个起点,如果你想真正把它转化成为面试和工作中能用到的能力,我还有几个建议。
第一,把代码提交到Git仓库,并且规范地写commit message。哪怕是个人项目,也要保持每个提交都“小而清晰”的习惯。我在面试候选人的时候,会习惯性看他的Git提交记录,那些提交信息写得乱七八糟的人,通常代码质量也不会好到哪里去。
第二,在项目里尝试编写单元测试,至少覆盖核心Service层的几个方法。你不需要做到测试覆盖率百分之多少,但只要你写了,面试官就会觉得你比大部分应届生更有工程素养。用Spring Boot Test + Mockito写几个简单的用例,一周时间就能入门。
第三,项目上线前,至少做一次基本的日志梳理。把Logback配置文件改一下,按天生成日志文件,区分info和error级别,打印请求耗时。这些操作在实际业务中会帮你节约无数排查问题的时间。我自己的习惯是给每个接口请求打一条简洁的access log,包含路径、耗时、状态码,出问题时先看这个日志再定位。
最后说几句大实话
我带过的学生里,凡是能把这个闲置物品交易平台从头到尾吃透、动手写一遍的,最后答辩和就业的结果基本都不差。关键是不要停留在“能跑就行”的层面,而是要搞清楚每一行设计背后的为什么。为什么订单表要单独存买家ID和卖家ID而不是通过商品表去关联?为什么商品状态和订单状态要分开管理?这些细节才是你和别的同学拉开差距的地方。
如果你拿到源码,我建议你先花半天时间把数据库表结构全部看懂,再花一天时间把从登录到下单的接口调用链路走一遍,最后再动手去改代码。不要上来就启动项目,然后对着界面一头雾水。代码可以帮你完成毕业设计,但只有理解了它,它才能真正帮你走好后面的职业路。