1. 毕设选题与需求拆解
每年毕业季,选毕设题目就像开盲盒。Java + SpringBoot + Vue 做二次元商品商城系统,名字听起来很热闹,但真正动手时你才会发现,从选题、建表、搭框架、写接口、切页面到最终打包部署,每一步都有隐藏的坑。这篇文章我以这个动漫周边商城项目为例,把整个从零到一的实现过程、取舍逻辑、调试经验和答辩要点一次性讲透。如果你正在做毕设、想快速上手前后端分离项目,或者刚从网上拿了一个项目但讲不清原理,下面的内容可以直接参考。
这个题目的本质是一个典型的电商后台系统,但比单纯的"增删改查"多了不少业务上的弯弯绕绕。用户端要能注册登录、逛商品、加购物车、下单支付,管理端要能管商品、管分类、管订单、管用户。看起来功能不少,但正是因为业务场景成熟、模块划分清晰,它才适合作为毕业设计——既能覆盖SpringBoot、Vue、MySQL、MyBatis-Plus这些主流技术栈,又有足够多的业务细节可以写进论文和答辩PPT。
1.1 为什么选动漫周边商城这个题目
首先是业务足够清晰。商城系统天然分成用户端和管理端两大类,每个功能模块都有成熟的教学案例可以参考,不至于出现"题目太抽象、不知道从哪下手"的问题。其次是场景有话题性,二次元周边商品有手办、景品、扭蛋、抱枕、画集等不同类型,每类商品的价格、库存、属性都不一样,这给数据库设计提供了很自然的多表关联练习机会。答辩的时候你把"手办预售款和现货如何处理库存"这种业务细节讲清楚,评委一听就知道你是真做了项目,而不是随便下载一个demo糊弄。
第三是技术栈主流。SpringBoot + Vue 的组合在就业市场和开源社区都是绝对主流,做完这个项目的过程中沉淀的知识,拿去找Java后端岗位的实习或校招完全够用。我见过不少同学做完这个模块后,直接把权限认证、购物车逻辑、接口设计写进简历,面试时被追问细节也能对答如流。换句话说,毕设做这个题目,你不是在为学分折腾,而是在为工作攒经验。
1.2 核心功能需求清单
这里给出一份我指导毕设时比较常用的功能清单,照着做基本不会漏功能,也能满足绝大多数学校对"工作量"的要求。
用户端:
- 注册登录:用户名注册、登录、JWT鉴权,密码加密存储
- 商品浏览:首页轮播图、分类导航、商品列表分页、商品详情
- 搜索筛选:关键字搜索、按分类筛选、按价格排序
- 购物车:加入购物车、修改数量、删除商品、选中结算
- 订单流程:确认订单、收货地址管理、模拟支付、订单状态流转
- 个人中心:个人信息维护、收货地址管理、我的订单列表、商品收藏
管理端:
- 后台仪表盘:商品数、订单量、用户数、销售额统计
- 商品管理:商品发布、上下架、库存管理、图片上传、逻辑删除
- 分类管理:商品分类的多级管理
- 订单管理:查看订单详情、发货处理、退款处理
- 用户管理:用户列表、禁用账号、角色分配
系统角色建议划分两大类:管理员 admin 和普通用户 user。权限控制可以用拦截器加注解实现,如果想在技术上增加亮点,则用 Spring Security + JWT 的方式。如果时间紧,拦截器方案完全够用,也更容易给答辩评委讲清楚。
2. 技术选型与项目架构设计
技术选型这部分,是写好说明文档和论文的关键。前面说"技术栈要主流",但主流不等于盲目堆新技术。有些同学为了显得高大上,非要上 Spring Cloud 微服务,结果一个订单系统拆了五六个服务,本地启动内存直接爆掉,答辩时链路都讲不利索,反而翻车。商城系统这种业务体量,单体架构完全够用,并且更能体现你对分层架构、数据库设计的理解。
2.1 技术栈的合理搭配
建议的核心技术栈:
- 后端:SpringBoot 2.7.x + JDK 8/11 + MyBatis-Plus + MySQL + Maven
- 前端:Vue 2.7 或 Vue 3 + Element UI / Element Plus + Axios + Vue Router + Pinia/Vuex
- 鉴权:JWT
- 接口文档:Swagger / Knife4j
- 文件存储:本地存储即可,加分项用 MinIO
- 缓存:单机 Redis 缓存热点商品数据(想加分可以加)
这里多说一句,MyBatis-Plus 是绝大多数毕设项目的默认选择,因为它内置了通用的增删改查方法,能省下大量重复SQL的编写时间。但用MyBatis-Plus不等于不会写SQL,我反而建议你把分页查询、多表关联这些核心SQL亲手写一遍,论文里能写的东西也更多。前端方面,Vue 2.7 + Element UI 的教程最多,踩坑成本最低;Vue 3 + Element Plus 更贴近当前新项目的流行方向,如果你对组合式API有基本了解,选 Vue 3 会让简历更好看。
2.2 前后端分离与项目分层
这个项目的架构是典型的前后端分离模式。后端 SpringBoot 提供 RESTful API,前端 Vue 通过 Axios 调用接口,数据格式统一为 JSON,鉴权信息放在请求头的 Authorization 字段。两者通过 HTTP 通信,开发时可以分别启动前后端项目,前端配置代理转发到后端端口,避免跨域问题。
后端项目内部建议划分出这几层:
- Controller 层:接收请求、参数校验、返回统一响应体
- Service 层:业务逻辑处理、事务管理
- Mapper 层:基于 MyBatis-Plus 操作数据库
- Entity 层:数据库实体类
- DTO / VO 层:接口传输对象,不要把数据库表结构直接暴露给前端
- Config 层:跨域配置、拦截器注册、MyBatis-Plus 分页插件配置
前端项目对应结构:
- src/api:按后端 Controller 模块拆分的 Axios 请求模块
- src/router:路由表,包含路由守卫
- src/views:页面组件,如首页、商品详情、购物车、订单、后台管理
- src/components:通用组件,如轮播图、商品卡片、Header、Footer
- src/store:用户状态、购物车数量等全局状态
- src/utils:Token 存取、请求封装等工具函数
2.3 数据库表设计
数据库是整个系统的地基,表结构设计好了,后面开发顺风顺水;设计不好,联调阶段各种返工。这个项目的核心表我建议这样设计:
用户表 user:id、username、password(BCrypt加密)、nickname、avatar、phone、email、role、status、create_time、update_time
商品分类表 category:id、parent_id、name、level、sort
商品表 product:id、category_id、name、subtitle、main_image、detail_images、detail_html、price、original_price、stock、sales、status、is_delete、create_time
购物车表 cart:id、user_id、product_id、quantity、selected
订单表 order:id、order_no、user_id、total_amount、pay_status、order_status、receiver_name、receiver_phone、receiver_address、create_time、pay_time
订单明细表 order_item:id、order_id、product_id、product_name、product_image、product_price、quantity
收货地址表 address:id、user_id、receiver_name、receiver_phone、province、city、detail、is_default
轮播图表 banner:id、link_url、image_url、sort、status
几个必须注意的细节。第一,密码不能明文存储,用 BCrypt 加密,Spring Security 自带实现。第二,商品表用逻辑删除字段 is_delete,不要物理删除,否则订单明细里关联的商品记录会断裂。第三,金额字段类型用 decimal,不要用 float 或 double,Java 里浮点数算钱会出精度问题,这个面试官也爱问。第四,订单编号需要在代码里生成,格式可以用时间戳加随机数,避免数据库自增主键直接暴露业务量。
3. 前后端核心模块的开发实现
这部分是我实际编码时的完整思路和关键代码,按照从后到前的顺序来推进,每一步都能和数据库表对应上。
3.1 搭建 SpringBoot 工程与统一响应体
先用 Spring Initializr 生成一个基础工程,再手动把依赖补全。核心依赖如下,版本尽量选稳定版,不要盲目追新:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi2-spring-boot-starter</artifactId> <version>4.4.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies>接下来先写统一响应体,这是所有接口的基础。我定义一个 Result 类,包含 code、message、data 三个字段,成功和失败的静态方法。这样前端可以根据 code 是否为 200 判断请求是否成功,错误信息也统一处理,而不是让前端每个请求都去 catch 异常。
public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> ok(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("success"); result.setData(data); return result; } public static <T> Result<T> error(Integer code, String message) { Result<T> result = new Result<>(); result.setCode(code); result.setMessage(message); return result; } }同时建议加一个全局异常处理器,用 @RestControllerAdvice 捕获业务异常和参数校验异常,返回统一的 Result.error。这样前端即使遇到参数错误,也能拿到明确的提示,而不是一屏的堆栈信息。这个细节很能体现代码规范意识,答辩时值得提一句。
3.2 JWT 登录鉴权与拦截器
登录鉴权是面试官最爱追问的模块,很多同学讲不清楚,我在这里把逻辑完全拆开。用户在登录接口提交用户名和密码,后端校验通过后,用 JWT 生成一个 token,里面包含用户 id 和角色信息,并设置过期时间。前端拿到 token 后存到 localStorage,每次请求时在 Axios 请求拦截器里把 token 放进请求头的 Authorization 字段。后端用拦截器拦住需要登录的请求,校验 token 是否有效,解析出用户信息放到请求上下文中。
核心拦截器代码大致如下:
public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token == null || token.isEmpty()) { throw new BusinessException(401, "未登录或登录已过期"); } Claims claims = JwtUtil.parseToken(token.replace("Bearer ", "")); Long userId = Long.valueOf(claims.get("userId").toString()); request.setAttribute("userId", userId); return true; } }注意:跨域配置必须允许 Authorization 请求头。我第一次整合时忘了在 CorsConfig 里配置 exposeHeaders,前端登录接口能通,其他带 token 的接口全部报跨域错误,排查了半天才发现是这里的问题。
管理员接口需要额外加一层角色校验。我的做法是在拦截器基础上,再定义一个 @RequireAdmin 注解,拦截器里判断当前用户角色不是 ADMIN 就返回 403。代码量不大,但权限分层很清晰。
3.3 Vue 路由守卫与前端权限控制
前端路由分两类:普通用户页面和需要管理员权限的后台页面。在路由表中给后台路由增加 meta.requiresAdmin 标记,然后在全局前置守卫中判断:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') const role = localStorage.getItem('role') if (to.meta.requiresAuth && !token) { next({ path: '/login', query: { redirect: to.fullPath } }) } else if (to.meta.requiresAdmin && role !== 'ADMIN') { next({ path: '/403' }) } else { next() } })需要强调一个原则:前端路由守卫只是用户体验层面的控制,真正的安全校验必须放在后端接口上。因为接口是可以被直接调用的,前端隐藏了按钮,不等于接口就安全了。所以我讲权限控制时总说,"前端控制是体验,后端控制是安全"。
Axios 封装也可以在拦截器里做统一处理,比如请求头带上 token,响应拦截器里遇到 401 自动跳转登录页,遇到 code 不等于 200 时弹出错误提示。这部分代码我在 src/utils/request.js 里统一管理,页面里不用重复写这些逻辑。
3.4 购物车与下单流程的实现
购物车的核心逻辑值得单独展开。购物车表设计得比较简单:cart(id, user_id, product_id, quantity, selected)。加入购物车时,先查用户购物车里是否已经有该商品,有则数量加一,没有则新增一行。修改数量时校验不能超过库存,同时刷新顶部购物车数量角标。
下单流程是系统的核心,也是事务和并发控制的重点。按顺序在 Service 层实现:
- 根据用户选中的购物车记录,批量查询商品的当前价格和库存
- 计算订单总金额,价格乘数量累加
- 校验库存是否足够,不足则抛出业务异常
- 生成订单号,格式建议为 yyyyMMddHHmmss 加 6 位随机数
- 插入订单主表和订单明细表
- 扣减商品库存,这里用 UPDATE 语句时可以带上 stock >= quantity 的 WHERE 条件,防止超卖
- 删除购物车中已下单的商品
- 模拟支付,直接标记订单为已支付,并记录支付时间
- 返回订单编号给前端
以上步骤必须放在同一个事务方法中,加 @Transactional 注解,否则中途异常会导致订单表和库存数据不一致。库存扣减那步,我在实际开发中更推荐用乐观锁或者 SQL 层的原子更新来保证并发安全,也就是说执行 UPDATE product SET stock = stock - #{quantity} WHERE id = #{id} AND stock >= #{quantity},返回值是 0 说明库存不足。
注意:订单明细表必须冗余商品快照,包括商品名称、图片、单价,而不是实时关联商品表。因为商品价格会变动,如果订单明细每次去查实时价格,商家一改价,历史订单的金额就全乱了。这个细节在做电商项目时非常关键,答辩时主动提出来,评委通常会认可你的业务理解。
3.5 接口文档与 Swagger 整合
接口写完以后,强烈建议整合 Knife4j 生成在线接口文档。这不光是给前端同学用的,也是论文里"系统实现"章节的重要素材。配置好之后,访问 /doc.html 就能看到所有接口的调试页面,Swagger 注解还能把接口的入参含义、返回结构描述清楚。后端接口全部联调通过后,截图放到论文里,整个工作量的真实感会强很多。
4. 部署调试、避坑指南与答辩准备
项目开发阶段只是第一步,能不能顺畅地在评委面前演示,才是毕业设计的生死线。我见过太多同学开发时一切正常,答辩前换台电脑就启动不起来,或者数据库连不上,当场翻车。所以部署调试这一环,必须提前踩好坑。
4.1 本地环境配置与项目启动步骤
我实测下来比较稳妥的启动流程如下:
- 安装 JDK 8 或 11,配好 JAVA_HOME
- 安装 Maven 3.6 以上版本,配置阿里云镜像加速依赖下载
- 安装 MySQL 5.7 或 8.0,执行项目里的 SQL 脚本初始化数据
- 修改 application.yml 里的数据库账号密码、服务端口
- 启动后端:
mvn spring-boot:run,或者mvn clean package后执行java -jar xxxx.jar - 启动前端:先
npm install,再npm run dev启动开发服务器 - 浏览器访问前端地址,开始前后端联调
这里有个操作建议:先在配置文件里把 MyBatis-Plus 的 SQL 日志打开,联调阶段在控制台里能看到每一条真实执行的 SQL,排查问题效率翻倍。等演示时再关掉日志,避免控制台刷屏。
4.2 高频问题排查速查表
我把调试这个项目过程中遇到的高频问题整理成一张表,可以直接拿来对照:
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
| npm install 报 ERESOLVE 错误 | 依赖版本冲突 | npm install --legacy-peer-deps |
| 前端请求后端接口 404 | 接口路径不一致或未配置代理 | 检查 @RequestMapping 路径与前端 api 路径,配置 Vite/Webpack 代理 |
| MyBatis-Plus 分页失效 | 缺少分页插件 | 在配置类注册 MybatisPlusInterceptor 和 PaginationInnerInterceptor |
| 数据库中文乱码 | 连接串未指定 UTF-8 | JDBC 连接串加 useUnicode=true 和 characterEncoding=utf8 |
| 登录后刷新页面状态丢失 | 用户信息未持久化 | 登录后写入 localStorage,路由守卫读取恢复 |
| 后端启动报 DataSource 配置错误 | application.yml 配置问题 | 检查 url、username、password 以及驱动依赖 |
| 端口占用 | 上轮进程没有杀掉 | Windows 下 netstat -ano 找 PID 后 kill |
除了这些技术问题,我再分享一个很土但很管用的思路:遇到 bug 时,先确定问题发生在哪一层。用 Swagger 或 Postman 直接调后端接口,如果接口返回的数据正确,问题一定在前端取数、渲染或者路由上;如果接口返回就是错的,再去查 SQL、事务、参数传递。这个分层排查的思路能帮你省下大量瞎试的时间。
4.3 答辩准备与项目亮点的提炼
答辩时不要只对着 PPT 念,要学会"主动亮技术点"。这个项目里能挖的亮点很多:JWT 无状态鉴权的好处、事务保证库存和订单一致性的方式、订单快照的设计思路、购物车选中结算的状态传递、MyBatis-Plus 分页插件的使用、全局异常处理机制。把你的设计取舍讲出来,比如"为什么用 JWT 而不用 Session",比单纯背代码更有说服力。
另外就是演示环境的准备。我建议准备一套干净的数据,商品图片统一尺寸,订单状态分布合理,这样演示的时候页面好看,也不会出现某个按钮点了没反应的尴尬。提前把新增商品、下单、发货、退款这几条主流程各走一遍,确保万无一失。
4.4 说明文档和论文的写作建议
标题里提到的"说明文档 + LW",通常指的是项目部署说明书和毕业论文。现在很多资源包会附带这些材料,但我更建议你在参考的同时自己重写一遍,因为只有自己写过的文档,答辩时被问到才会答得上来。
说明文档一般包含:项目简介、技术栈、环境搭建步骤、功能模块说明、数据库表结构说明、部署配置方法。写的时候重点写清楚每一步怎么操作,最好配上截图。论文则按学校模板来,通常包括摘要、绪论、需求分析、系统设计、系统实现、系统测试、总结展望。写系统测试时不要只写"测试通过",要列出测试用例、预期结果、实际结果,这能体现你具备基本的测试意识。
5. 调试定制与改题思路
毕设过程中你需要面对的一个现实问题是:学校的题目要求和网上的现成项目可能不完全一样,比如有的要求加"秒杀功能",有的要求加"邮件通知",有的要求改成"积分商城"。这时候就需要理解系统的扩展点在哪里,才能快速做定制。
5.1 常见定制需求的改法
如果要加商品收藏功能,只需要新增一张 favorite 表,字段是 id、user_id、product_id、create_time,然后在商品详情页加一个收藏按钮,后端增加收藏、取消收藏、查询收藏状态三个接口。这个改动半天就能完成,完全不用动现有表结构。
如果要加秒杀功能,则在商品表增加秒杀价、秒杀开始时间、秒杀结束时间这几个字段,前端用定时器控制按钮的倒计时状态,后端在下单事务里校验当前时间是否在秒杀窗口内,扣减库存时使用乐观锁。秒杀是并发控制的典型场景,做得好答辩时很加分。
如果要接真实支付,可以用支付宝沙箱环境。前端在确认订单页点击支付后跳转沙箱支付页面,后端配置支付成功后的异步回调接口,在回调中更新订单状态为已支付。这一步配置繁琐,但完成后的系统完整度和商业感会明显上一个档次。
5.2 团队协作与联调经验
如果是两个人合作这个项目,分工要提前说清楚,最好一个人负责后端接口,一个人负责前端页面,但数据库设计和接口文档两个人必须一起参与。我见过太多组前期不好好设计表结构,到联调阶段才发现字段对不上、返回值格式不统一,最后加班到崩溃。
联调阶段的黄金法则是:先保证后端接口全部能用 Swagger 调通,再让前端接入。只要接口能返回预期 JSON,前端剩下的问题基本就是取值、渲染、路由这些小事。这个流程养成了,后面做更大的项目也能省下大量时间。
文档版本管理也很重要,接口参数改一次,文档就要同步更新一次。我自己的习惯是每次改完接口,立刻在 Knife4j 上刷新一下,并把变更点写在项目根目录的 interface-change.log 里,这样连队友都能看到改动记录,不会出现两个人各改各的、最后合不上代码的情况。
最后再分享一点个人经验:这个项目做完之后,不要急着删代码,把它当成你自己的作品集。把登录鉴权、购物车、订单状态机、事务处理这些模块的代码重新整理一遍,加上注释,拍照或者录个屏存下来。找工作时把这些真实项目的截图和演示视频放在简历附件或者作品链接里,比写在简历里干巴巴一句"熟悉SpringBoot"有说服力得多。做毕设的过程是痛苦的,但这份代码和文档,会在你后面很多场合给你底气。