做 Java Web 方向的毕业设计,这两年我接触下来的项目里,出现频率最高的一个题目就是校园闲置物品交易系统。原因其实不难理解:选题贴近校园生活,功能边界相对清晰,一套 SpringBoot+Vue 的前后端分离结构又在企业开发里非常常见,做完既不显单薄,也好向评委老师和面试官交代。但同一道题,交付质量的差距能拉到非常大:有人拿到的所谓“完整项目源码”环境都起不来,SQL 脚本跑到一半报错,接口文档光有目录没有内容,最后只能靠答辩现场编故事硬撑。这篇文章我就拿一套能真正跑通的校园闲置物品交易系统为例,把技术选型、库表设计、后端接口、前端页面、SQL 脚本、打包部署这条完整链路逐段拆开讲,顺带把项目从启动到上线会遇到的典型坑都列出来。
拿到一份“完整项目”之后,我自己的检查顺序通常很固定:先看能不能一键启动,再看 SQL 脚本能不能一次跑通,最后把前端构建产物和后端静态资源那块理清楚。这三关过了,项目才算真正完整。文章里讲的思路和参数配置都是我在实际调试中验证过的,你可以直接拿来参考,也可以照着检查自己手上的代码。
1. 整体设计与技术选型
1.1 为什么是 SpringBoot+Vue,而不是其他组合
校园闲置物品交易系统这种业务形态,核心诉求是:信息展示、用户登录、商品发布与交易流程管理。它没有很高并发压力,也没有复杂的实时计算,但要求开发效率高、演示效果好、扩展空间足够,这几条SpringBoot+Vue的组合正好全部满足。
SpringBoot 的优势不用多说,它把 Spring 家族那一堆繁琐的 XML 配置降到了最低。内嵌 Tomcat 意味着不用单独安装容器再部署 war 包,执行mvn spring-boot:run或者直接启动主类就能跑起来。配合 MyBatis-Plus 操作数据库,CRUD 代码能缩到很短,这对毕设周期的项目来说非常合适。Vue 这边,按组件写页面、用路由切换视图,整个前端维护起来比纯 jQuery 舒服太多,而且 Vue 打包后的静态文件完全可以交给 SpringBoot 托管,做到“一个 jar 包启动整个系统”的效果。
有人会问,为什么不直接用 JSP 做服务端渲染?这就要说回现在的主流开发习惯了:前后端分离的理念已经进入招聘和面试的默认语境,毕设项目用 Vue 做前端,至少能向评委展示你有“接口对接”“跨域处理”“静态资源部署”这方面的实战意识。纯 JSP 虽然也能实现功能,但从技术展示的角度讲就吃亏了。我接手过不少同学自己写的 JSP 版校园二手交易系统,功能确实有,但答辩被追问“为什么不用主流框架”时,回答起来会比较吃力。
Vue 版本的选择上,如果拿到的项目是基于 Vue 2 + Element UI 的,别急着说它旧。Vue 2 的生态成熟稳定,Element UI 在数据管理后台场景下开箱即用,很多企业的存量项目到现在还是这套组合。如果自己新建项目,我也建议先从 Vue 2 起步,把路由、Axios、组件复用这些基本功吃透,再去看 Vue 3 的组合式 API 也不迟。这个选择能少踩非常多版本兼容的坑。
1.2 用户端和管理端的功能模块怎么拆
系统整体拆成两个入口:普通用户端和管理员后台。不用微服务划分,一个单体 SpringBoot 内部分模块即可,重点是业务逻辑要分层清楚。
用户端核心功能,按使用流程排一下:
- 注册登录:手机号或学号注册,密码加密存储,登录后拿到 Token 凭证。
- 商品浏览:首页展示在售闲置,支持分类筛选、关键词搜索、按价格或时间排序、查看商品详情。
- 商品发布:填写标题、描述、价格、成色、实物图片,提交后进入待审核状态。
- 个人中心:管理我发布的商品(上下架、编辑、删除)、查看收藏的商品、查看买到的和卖出的订单。
- 留言互动:在商品详情页给卖家留言询问,卖家可以回复。
管理员端功能,对应平台运营的需要:
- 登录与主控台:管理员独立入口,首页显示商品总数、用户总数、订单数、待审核数量等统计信息。
- 商品审核:对新发布的商品做审核,通过或驳回,驳回时填写原因。
- 分类管理:增删改商品分类,比如数码、书籍、衣物、生活用品等。
- 用户管理:查看用户列表、禁用违规账号、查看单个用户发布的商品。
- 订单管理:查看全部订单和订单状态流转,必要时处理纠纷。
很多同学做这个项目时,容易把系统做成“人人都是管理员,统统都能删”。这其实不是加分项,反而会被评委抓住追问权限控制。上面这套拆法,用户和管理员两边的操作边界是清楚的,数据库里通过角色字段区分,代码里通过拦截器校验,答起辩来非常顺。把商品审核环节放进来,是因为校园闲置交易场景下平台审核是合理需求,这一设计还能把项目从“纯展示+下单”提升到“带后台运营闭环”的完整度。
2. 数据库设计与 SQL 脚本体检
2.1 核心表结构怎么定才不乱
数据库设计是评委最爱问的地方,也是项目能不能支撑功能扩展的地基。这里的核心表大概有 7 张:用户表、商品分类表、商品表、商品收藏表、订单表、留言表、公告表(公告可选)。
用户表字段,建议按下面这套来:id 主键、username 登录账号、password 加密后的密码、nickname 昵称、avatar 头像地址、phone 手机号、student_no 学号、role 角色(USER/ADMIN)、status 状态(1 正常/0 禁用)、create_time 创建时间。注意一点,密码字段一定不要用明文,也不要只做简单 MD5,推荐 BCrypt 或者至少加盐。MySQL 里 password 字段长度建议设成 60,用于存 BCrypt 加密结果,如果设成 32 存 MD5,表结构设计上会被人扣分。
商品分类表很简单:id、name、sort 排序号、create_time。分类表不一定要做得多复杂,但建议在商品表上保留 category_id 字段,并且冗余存一个 category_name,这样列表查询时可以少一次关联表查询,性能上更干净。
商品表字段略多,我列一份实际验证过的结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| user_id | bigint | 发布者用户 id |
| category_id | bigint | 分类 id |
| category_name | varchar(20) | 冗余分类名 |
| title | varchar(50) | 商品标题 |
| description | text | 商品描述 |
| price | decimal(10,2) | 出售价格 |
| original_price | decimal(10,2) | 原价/参考价 |
| condition_level | tinyint | 成色,如 1到5 表示几成新到全新 |
| images | varchar(1000) | 图片地址,多个逗号分隔 |
| status | tinyint | 商品状态:0 待审核 1 在售 2 已售出 3 下架 4 驳回 |
| views | int | 浏览量 |
| create_time | datetime | 发布时间 |
商品表的 status 字段是整个系统的业务关键。不要用简单的“0/1”表示上架下架,而要做成上面这样的状态集合。理由很简单:订单模块需要“已售出”这个状态来阻止商品被重复购买,管理员审核需要“待审核”和“驳回”,这些状态如果不提前在表里设计好,后面做业务逻辑时一定到处打补丁。这是个很典型的“多花五分钟设计状态机,后面省下五个小时改 Bug”的例子。
订单表同样重要:id、order_no 订单编号、goods_id、seller_id、buyer_id、price 成交价、status 订单状态(0 已取消/1 待确认/2 交易中/3 已完成)、message 备注、create_time、update_time。这里要特别提醒:不要只在订单表里存 goods_id,把卖家和买家 id 也冗余出来。很多同学觉得“卖家可以通过商品表查”,实际上订单列表页需要频繁按用户查订单,冗余这两个 id 可以省掉大量联表查询,这是从性能角度出发最简单有效的一个技巧。
留言表字段:id、goods_id、sender_id、receiver_id、content、reply_to_id(回复哪条留言)、create_time。收藏表更简单:id、user_id、goods_id、create_time,但必须给 user_id + goods_id 加唯一索引,防止重复收藏,也方便查询去重。
2.2 表关系与字段设计的常见错误
我检查过不少同学实现的校园交易系统,表设计雷区集中在几个地方。
第一是重复造轮子存状态。有的项目不再做订单表,而是把商品表里加一个“购买者ID”,这种做法的后果是:同一个商品如果有历史订单,数据会被覆盖,交易记录完全丢失。交易系统没有订单流水,业务上说不通,答辩也经不起问。这一点必须用独立订单表去落地。
第二是时间字段类型混乱。创建时间用 varchar 存字符串,更新时间靠手工维护,排序和统计时非常难受。推荐的写法是:create_time 用 datetime,update_time 用 datetime,并在插入时显示赋值,或者依赖 MyBatis-Plus 的自动填充功能。日期类型明确,后面的按天统计、按时间排序才不会出错。
第三是图片字段设计。一个商品有多张图,有人喜欢建一张子表,有人用 JSON 数组字段,有人用逗号分隔。毕设项目里最稳妥的是 images varchar(1000) 逗号分隔,前端 split 后渲染成轮播图或缩略图。这样做的好处是表结构简单,查询商品时一次取回所有图片,不用额外联表,性能和对开发友好度都够。如果以后要支持更复杂的图片管理,再拆表也不迟。
第四是删除策略。商品表不要物理删除,状态字段里已经有“下架”,用户删除自己的商品时,把 status 改成 3 或者加一个 delete_flag 即可。这样能保留发布历史,后台管理员也能追溯。用户表同理,禁用账号用 status 字段控制,别直接 delete 记录,否则会破坏商品和订单的关联完整性。
2.3 SQL 脚本里的隐藏细节:字符集、索引、测试数据
提供 SQL 脚本时,很多人一上来就是 DROP TABLE 然后 CREATE TABLE,能跑,但细节上经不起推敲。一份合格的 SQL 脚本,至少要包括四部分:建库语句、建表语句、索引设计、初始化数据。
建库语句最常见的坑是字符集。先执行CREATE DATABASE campus_trading DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;。为什么必须用 utf8mb4?因为商品描述里可能输入 Emoji 表情,MySQL 的老 utf8 字符集存不下 4 字节字符,一旦用户输入了,插入直接报错。我在实际调试中不止一次被这个坑折腾过,排错到最后发现问题都指向字符集。所以第一步建库就把字符集定死,后面省心。
索引设计上,商品表建议建这几个索引:category_id 普通索引、user_id 普通索引、status 普通索引,三个字段也可以做联合索引,但毕设规模其实单列索引就够用。再强调一次收藏表的 user_id + goods_id 唯一索引。订单表 order_no 要建唯一索引,用于保证订单号绝对唯一,生成订单时用日期拼接随机数,极端情况下还是可能重复,索引兜底是最稳的。
初始化数据这件事,很多源码里完全缺位。空数据库启动项目,登录后首页一片白,演示效果大打折扣。所以 SQL 脚本里应该预置:一个管理员账号(admin/123456,密码是 BCrypt 密文)、两三个测试用户、八个左右的分类数据、十几条覆盖不同分类的测试商品数据。测试数据不是随便编的,商品标题、描述、价格要符合校园场景,图片建议用项目自带静态图,否则前端加载不到图片又是一堆红叉。我写测试数据时的习惯是:商品名称做成“九成新《高等数学》上下册”“可折叠小台灯”“捷安特山地自行车”这种真实感很强的文案,演示时评委一眼就能看出这是个能用的系统。
3. 后端 SpringBoot 实战实现
3.1 工程分层与关键依赖配置
SpringBoot 后端项目的目录结构,建议按经典的 Controller、Service、Mapper 三层来组织。不要为了显示自己能耐把所有逻辑写成一张“上帝 Service”,也不要再拆出各种花里胡哨的抽象层,这个规模的项目,三层足够清晰,答辩也好讲。
分包大概是这样的:config 放配置类,比如跨域配置、MyBatis-Plus 分页插件、拦截器注册;controller 接收前端请求,调用 service,返回统一结果;service 处理业务逻辑,像商品发布、订单流转这些核心流程;mapper 配合 MyBatis-Plus 直接操作数据;entity 放数据库表对应的实体类;vo/dto 放前端需要的视图对象和请求参数对象;common 放统一返回结果类、状态枚举、异常处理类;util 放 JWT 工具类。
用 Maven 作为项目构建工具,这是目前 Java Web 项目的主流做法。pom.xml 里最重要的依赖我列一份常用组合:spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、lombok、jjwt、hutool(可选,方便做日期、文件、ID 生成)、spring-boot-starter-validation 用于参数校验。
pom 里容易出问题的有两个点。一个是 SpringBoot 2.x 和 3.x 的选择,校园项目本地环境大多是 JDK8/MySQL5.7 或 8.0,所以 SpringBoot 2.7.x 是非常稳的选择,依赖兼容性好,资料也多。如果拿到的是 SpringBoot 3.x 项目,要注意 JDK 必须是 17 及以上,servlet 包名从 javax 变成 jakarta,MyBatis-Plus 要用对应的 3.5.3+ 版本,这些不匹配是启动报错的重灾区。另一个是 Lombok 的版本要和 JDK 对应,JDK8 配 1.18.28 左右基本没问题。
application.yml 的核心配置其实就几项:服务端口 server.port 默认 8080;数据库连接 datasource 里要带useUnicode=true&characterEncoding=utf8和serverTimezone=Asia/Shanghai;MyBatis-Plus 开启下划线转驼峰;再配一下日志输出级别。这里特别提醒:数据库连接串里的时区参数不要省,不然 MySQL 8 连接时会提示时区识别失败,项目启动直接失败。这个报错我帮别人排过太多次了。
3.2 登录鉴权与统一返回体
登录这块,主流方案是 JWT,而不是传统 Session。因为项目是前后端分离,后端接口不关心前端在哪里跑,只要请求头里带一个合法的 token,就能识别当前登录用户。JWT 的实现也简单:登录成功时用用户信息生成 token,设置两小时或稍长的有效期,返回给前端;前端后续每个请求在请求头 Authorization 里带上。后端写一个拦截器,在进入 controller 前校验 token,把用户 id 放到 ThreadLocal 里,service 层直接取。
拦截器里要注意白名单设计。用户登录、注册、浏览商品列表、查看商品详情这些接口不需要登录就能访问,必须配置排除路径;发布商品、下单、后台管理这些就必须校验权限。注册拦截器的示意代码:
registry.addInterceptor(jwtInterceptor) .addPathPatterns("/**") .excludePathPatterns( "/api/user/login", "/api/user/register", "/api/goods/list", "/api/goods/detail/**" );这段只是示意,路径要跟接口文档对齐。管理员接口最好单独再校验角色,拦截器里从 token 解析出 role,不是 ADMIN 的就返回 403。这个设计既是答辩加分点,也是防止“普通用户调管理员接口删数据”这类低级漏洞的关键。
统一返回体这个点,很多新手会忽略,但接口文档能不能写清楚全靠它。定义 Result 类,结构是 code、message、data 三个字段,code 为 200 表示成功,401 表示未登录或 token 过期,500 表示服务端异常。所有 controller 方法都返回 Result,统一之后前端 Axios 拦截器也好统一判断。这个设计不是可选项,而是减少前后端联调扯皮的核心习惯。
3.3 核心业务接口的编写要领
把商品发布、下单、状态流转这几个核心接口的实现思路讲清楚,这基本就是整个后端项目的题眼。
先说商品发布。前端提交的是一个表单,包含标题、描述、价格、分类 id、几张图片地址。后端先做参数校验:标题不能为空、价格必须大于 0、分类 id 必须存在。校验通过后,把当前登录用户设为 user_id,商品 status 默认设为 0(待审核)。这里有个细节:前端上传图片时,图片已经先通过独立的图片上传接口存到了服务器并返回 URL,商品发布接口里只存这个 URL 列表,不要在发布接口里同时处理二进制文件。图片上传接口单独拆出来的好处是:前端可以边选边传,发布表单提交时只剩 URL 拼接,提交速度快,失败也能定位是图片问题还是表单问题。
再看下单。下单是整个系统最容易写崩的接口,因为要处理并发下的重复购买。虽然毕设项目并发量不高,但代码习惯要从一开始就正确。下单流程大概是:根据商品 id 查商品,发现 status 不是 1(在售),直接提示“商品已下架或已售出”,结束;然后生成订单号,订单号用日期 + 用户 id + 随机数组成,保证唯一;接着把商品状态改成 2(已售出),插入订单记录。关键操作是“先校验状态、再改状态、再插入订单”。为了稳妥,可以在 update 时加条件 status=1,影响行数为 0 说明被人买走了:
boolean updated = goodsService.update( new LambdaUpdateWrapper<Goods>() .eq(Goods::getId, goodsId) .eq(Goods::getStatus, 1) .set(Goods::getStatus, 2) ); if (!updated) { throw new ServiceException("商品已被购买或已下架"); }这一步判断做完,后面的订单插入才有意义。很多人的订单接口出 Bug,都是因为先 insert order 再把商品状态改成已卖出,商品状态修改失败时订单却已经生成,数据就永远对不上了。
状态流转这块,建议做成枚举或常量类。订单状态:订单创建后待确认,卖家确认后交易中,买家确认收货后已完成,任意一方取消变成已取消。交易是校园当面交易,不涉及真实线上支付,这一点在项目说明里要写清楚,评委不会因为没接入支付扣分,反而会觉得你业务边界设计合理。如果之后想提高含金量,可以接沙箱支付,或者把“平台担保交易”做成可选订单状态,但那是后话。
4. 前端 Vue 实现与前后端联调
4.1 工程初始化与路由处理
Vue 项目按 Vue 2 + Element UI 的组合来讲,这是校园毕设项目里最常见的落地方案。环境上需要 Node.js(建议 14 到 16 版本,太高或太低都容易出现依赖安装问题)、npm 或 yarn。初始化项目用 Vue CLI 4.x 或 5.x,命令无非是vue create campus-trading-ui,选择 Vue 2 预设,再加 vue-router 和 axios。装依赖这件事,npm install 经常因为网络问题报错,常规解法是换镜像源,npm config set registry https://registry.npmmirror.com。这个操作每年都能卡住一批人,提前配好能省不少时间。
路由层的设计,最好做成“用户端 + 管理端分开处理,再合并路由表”。router/index.js 里把页面路径列出来:/login 登录页、/ 首页(商品列表)、/goods/:id 商品详情、/publish 发布商品、/profile 个人中心(下面挂子路由)、/admin 管理后台(下面挂子路由)。
路由守卫是必须写的。在router.beforeEach里判断目标路径是否需要登录,需要的话再检查 localStorage 里有没有 token,没有就跳去登录页,登录成功后带着 redirect 参数跳回来。这层守卫写好了,用户乱敲路径也不会进入未授权页面。管理员路由再叠加角色判断,权限闭环就成立了。这里也顺便解释了 vue-router 在项目里的实际用途:不只是跳转页面,更是权限控制的第一道门。
4.2 Axios 封装、跨域与图片回显
Axios 不要在每个页面里一遍遍 import 然后裸请求,统一建一个 request.js 封装。核心就是三件事:baseURL 统一指向后端地址;请求拦截器把 token 从 localStorage 取出,塞进请求头 Authorization;响应拦截器统一处理后端返回的 Result 对象,code 为 200 就返回 data,code 为 401 就清掉 token 并跳登录页,其他 code 用 Element 的 Message 组件弹出错误提示。这套封装写完,前端所有接口调用代码能缩到很短。
跨域问题是前后端分离开发里最经典的坑。开发环境下,前端跑在 8080,后端跑在 8081,两个端口不同,浏览器会拦截跨源请求。解决办法有两个,选一个就行。
第一种是后端开启跨域支持。SpringBoot 写一个配置类实现 WebMvcConfigurer,或直接在接口上加 @CrossOrigin 注解。第二种更推荐:前端在 vue.config.js 里配置 devServer.proxy,把 /api 开头的请求代理到后端地址:
devServer: { port: 8080, proxy: { '/api': { target: 'http://localhost:8081', changeOrigin: true } } }前端请求 /api/user/login,开发服务器直接转发给后端的 8081 端口,浏览器看到的是同源请求,跨域问题自然解决。这两种方案都能用,但我建议前端代理为主,后端跨域配置只作兜底,因为打包部署时前端静态文件和后端接口往往会在同一个站点下,代理配置才是稳定可靠的方案。
图片回显是另一个高频报错点。上传图片返回的是相对路径,比如 /upload/goods/123.jpg,前端在开发环境下访问的是 localhost:8080/upload,但图片实际存在后端的 8081 端口,自然显示不出来。解决办法是前端图片 src 拼上后端地址前缀,或者走代理规则把 /upload 也代理到后端,在 vue.config.js 的 proxy 里加一条'/upload': { target: 'http://localhost:8081' }。打包部署后,路径处理逻辑要再切回来,因为 jar 包内后端已经托管静态资源了,直接用相对路径访问就行。
4.3 打包构建:把 Vue 放进 SpringBoot
这一步是整个项目能否“一个 jar 跑全部”的关键,也是我检查项目时重点看的地方。先说流程:Vue 项目执行npm run build,生成 dist 目录,里面是 index.html、CSS、JS 等静态文件;然后把这个目录统一拷贝到 SpringBoot 的 resources/static 目录下。SpringBoot 默认把 static 目录当作静态资源目录,所以访问 http://localhost:8080 时,index.html 会直接被返回。
要注意一个问题:Vue 路由用了 html5 history 模式时,直接访问 http://localhost:8080/goods/1 会 404,因为后端没有这个路径对应的 controller,静态资源服务器也找不到这个物理文件。解决办法有两个:后端写一个转发 Controller,把非 /api、非 /upload 的路径都转发到 index.html;或者把前端路由改成 hash 模式,地址变成 /#/goods/1,刷新就不会 404。毕设项目里很多人图省事用 hash 模式,但我更建议写一个 controller 做转发,这样路由看起来干净,答“为什么不用 #”时也有话说。
前后端接口地址联调也要做一次统一。开发环境里前端 baseURL 可以配成空字符串或 /api,依赖代理转发;生产环境里前后端同源,baseURL 直接写 /api 即可。这个配置必须在打包前检查好,不然测试环境调通的接口,部署后一打开全是 404。我见过太多项目就是在这一步翻车的。
5. 接口文档与自测要点
5.1 接口文档怎么写才不水
接口文档是毕设项目里最容易流于形式的部分。不少人的“接口文档”就是把 controller 方法名抄一遍,没有参数说明,没有返回值结构,这样的文档自己都看不懂,更别说指导联调。一份合格的接口文档应该给每个接口标注清楚以下信息:接口地址、请求方式、是否需要登录、请求参数(字段名、类型、是否必填、含义)、返回结果示例、错误码说明。
推荐用 Apifox 或者 Postman 边测边生成接口文档,比手写 markdown 效率高得多,导出也方便。如果答辩用,建议整理成 PDF 附在项目说明里,评委想翻细节时看着舒服。下面列三个核心接口的文档示例,感受一下规范程度。
| 接口 | /api/user/login | /api/goods/release | /api/order/create |
|---|---|---|---|
| 方式 | POST | POST | POST |
| 是否登录 | 否 | 是 | 是 |
| 参数 | username, password | title, description, price, categoryId, images | goodsId |
| 返回 | code, message, data{token, userInfo} | code, message, data{goodsId} | code, message, data{orderNo} |
| 失败情况 | 用户名或密码错误 | 参数校验失败 | 商品已售出、未登录 |
接口文档里的返回体格式要跟代码里 Result 类完全一致,字段命名统一用驼峰,别一会儿 user_name 一会儿 userName。前端对接时最讨厌的坑就是字段名不一致,明明后端有数据,前端读不到。
5.2 接口自测的完整流程怎么走
项目写完不能直接丢给答辩。建议至少按下面这条链路走一遍完整自测:
第一步,启动 MySQL 和 SpringBoot 后端,确认控制台没有任何报错,数据库连接成功。第二步,启动 Vue 前端开发服务器,登录页面能出来,输错密码有报错提示。第三步,注册一个测试用户,用这个用户登录,拿到 token。第四步,发布一个新商品,带图上传,去数据库查这条记录,status 应该是 0。第五步,用管理员账号登录后台,找到这个待审核商品,审核通过。第六步,回到前端首页,搜索到这个商品,点进去看详情。第七步,换另一个测试用户下单,确认购买,再回数据库检查商品 status 变成 2,订单记录生成。第八步,卖家确认订单,买家确认收货,订单状态变成已完成。
这条链路走完,系统的主要功能闭环就验证过了。我见过不少同学答辩现场翻车,都是因为演示时走了一条以前没走过的路径——比如点了审核通过按钮发现接口 500,或者下单时发现商品状态不对。提前按这条路走一遍,能排除掉绝大多数低级问题。
6. 常见问题与排查技巧实录
6.1 启动阶段:端口、依赖、数据库连接三座大山
启动阶段遇到报错,90% 集中在三个地方:端口被占用、依赖版本冲突、数据库连不上。
端口占用。SpringBoot 默认 8080,如果本机已经跑了其他服务,启动会报 “Port 8080 was already in use”。处理办法,要么关掉占用端口的进程,要么在 application.yml 里改成 8081。Windows 下查端口用netstat -ano | findstr 8080,查到 PID 之后taskkill /PID 进程号 /F,本地调试很快。前端 Vue 默认端口也是 8080,一旦后端先启动占了 8080,前端启动也会冲突。所以我一般习惯让后端用 8081,前端保持 8080,两边互不干扰,代理转发也少改一处。
依赖版本冲突最让人抓狂。MyBatis-Plus 的版本、SpringBoot 的版本、MySQL 驱动版本三者必须兼容。典型问题:SpringBoot 3.x 项目,用了低版本的 mysql-connector-java,启动直接报驱动类找不到;还有 SpringBoot 2.x 项目配 MySQL 8 的驱动,驱动类名已经改成 com.mysql.cj.jdbc.Driver,配置不更新就连不上。多看一眼 Maven 仓库里各依赖的版本号,比在报错日志里猜半天有效得多。
数据库连不上的常见报错是 “Access denied for user” 或 “Connection refused”。前者是账号密码或权限问题,后者要么 MySQL 没启动,要么连接串端口写错。MySQL 8 还经常遇到 Public Key Retrieval is not allowed 的报错,解决方式是在连接串上加上allowPublicKeyRetrieval=true&useSSL=false。这个是 MySQL 8 安全策略导致的,不加就连不上,加上就通了。
6.2 联调阶段:跨域、接口 404、图片不显示
联调阶段的坑我整理成一个速查表,排查时对号入座。
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
| 前端访问接口报 CORS error | 后端未开启跨域或前端代理未配置 | 后端加 CORS 配置,或前端 devServer 代理 /api |
| 前端请求 /api/user/login 返回 404 | 请求打到了 Vue 自己的服务上 | 检查 vue.config.js 的 proxy 配置 |
| 调用后端接口返回 404 且后端无日志 | 后端 controller 路径写错 | 对齐接口文档和后端路径 |
| 发布商品后列表页图片不显示 | 图片 URL 是相对路径,前端拼错前缀 | 代理 /upload 到后端,或拼完整地址 |
| 登录后跳转页面刷新就 404 | Vue history 模式未做回退 | 后端转发非接口路径到 index.html,或改 hash 模式 |
| 数据库写入中文变成 ?? | 字符集未设置 utf8mb4 | 检查数据库、表、连接串字符集 |
自测时如果遇到接口返回 200 但 data 为 null,多半不是联调问题,而是后端业务逻辑抛了异常但被吞了。建议在拦截器那一层加一个全局异常处理器,把异常信息返回给前端,而不是让前端在页面上干瞪眼。全局异常处理这几十行代码,对排查问题的价值是几何级放大的。
6.3 答辩准备与进阶扩展
最后说点跟毕设直接相关的准备心得。答辩时老师最常问的三个问题:这个项目的难点是什么?数据库为什么这样设计?哪些模块是你独立完成的?对应这三个问题,准备的时候要理清楚三条线:并发下单时防止重复购买你是怎么处理的;商品状态机(待审核-在售-已售出-下架)为什么这样设计;哪些功能是自己从零写的,哪些参考了网上代码,参考的部分你又改了什么。
向上扩展的话,这套项目能加的点很多。商品搜索从 MySQL LIKE 换成 Elasticsearch 或者引入中文分词;热门商品缓存到 Redis;公告和轮播图做成可配置模块;下单后接一个模拟支付页面;甚至可以加入卖家地址和预约时间的加锁设计。任何一个点都能在答辩时展开讲五分钟。
7. 从零到一:完整跑通项目的操作清单
7.1 环境准备与版本对照
很多项目拿到手跑不起来,不是代码有问题,而是环境版本跟项目要求对不上。我列一个从实践出发的推荐版本表,注意并不是说只有这套版本能跑,而是这套组合被验证过问题最少。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 17 | SpringBoot 2.7 用 JDK8;3.x 用 JDK17 |
| Maven | 3.6 以上 | 用 IDEA 自带或单独安装都行 |
| Node.js | 14 到 16 | Vue2 项目在这个区间最稳 |
| MySQL | 5.7 或 8.0 | 8.0 需要在连接串加时区参数 |
| SpringBoot | 2.7.x | 校园项目首选 |
| Vue | 2.x | 配合 Element UI,生态稳 |
7.2 十分钟启动步骤
把整个项目的启动流程压缩成一套最小步骤,按顺序执行基本不会出问题。
第一步:在 IDEA 里打开后端工程,确认 Maven 已经自动拉完依赖,如果 pom.xml 上显示报红,先执行 mvn clean install 或者刷新 Maven 项目,看报错信息再处理依赖。
第二步:用 MySQL 客户端执行 SQL 脚本,执行完确认表数量和测试数据条数都对,这里有一步很重要,确认数据库名和账号密码要和 application.yml 里的 datasource 配置一致。配置文件里改成本地 root 账号和密码,或者新建一个专用账号,取可读性强的名字。
第三步:启动后端主类,看到 Spring Boot 启动成功日志,端口 8081 或者你自己配的端口能访问,说明后端已经就绪。
第四步:进入前端目录,执行 npm install 安装依赖,再执行 npm run serve。启动成功后访问 localhost:8080,应该能看到首页。
第五步:用 SQL 脚本里的管理员账号登录后台,用预置测试用户登录前台,走一遍商品发布、审核、下单、完成订单的闭环。
这套流程我在不同版本的源码上验证过很多次,照着走下来,半个小时足够把一个新环境从零跑通。如果中间有一步卡住,回到上一节的问题速查表里找对应解法就行。