1. 毕业设计选题的真相:这套"米家商城"到底值不值得选
先说结论:如果你正在找一套能顺利答辩、能写进简历、又不至于让自己写到吐的JavaWeb毕设题目,SpringBoot+Vue+MySQL的商城类系统几乎是公认的"安全牌"。原因很简单,它把后端、前端、数据库三样东西全部覆盖了,而且业务复杂度可控,既不是纯CRUD那种一眼假的项目,也不会像秒杀系统那样把自己埋进高并发的坑里。
这套米家商城项目,实际做出来是一个典型的前后端分离商城系统。用户端包含注册登录、商品浏览、购物车、下单支付(模拟)、订单管理;后台管理端包含商品管理、分类管理、订单处理、用户管理等。名字叫"米家"但不需要真的去对接任何智能家居硬件,它就是一套标准电商业务闭环的毕业设计项目。适合的人群很明确:正在做毕设的本科生、想练手SpringBoot全栈的初学者,以及需要完整源码加论文结构作参考的应届生。
我之所以说这个题目性价比高,关键在于它踩中的考察点:答辩老师通常不会逐行看代码,但一定会问你"前端怎么跟后端通信""数据库为什么这么设计""订单和库存怎么保证一致性"。而这些点,恰好都在这个项目的能力范围内。把这三板斧讲清楚,答辩基本就稳了。
1.1 从标题拆开看:SpringBoot+Vue+MySQL的组合能证明什么
先说SpringBoot。它的价值不在"框架本身多厉害",而在于它帮你省掉了一堆传统SSH时代要手工搞的配置。SpringBoot本质上是对Spring全家桶的一次封装,内嵌Tomcat、自动配置、Starter机制,让后端开发可以像搭积木一样起步。商城系统里最常见的几个后端模块——用户模块、商品模块、订单模块——全部可以用SpringBoot的Restful接口来暴露给前端。
再说Vue。Vue是前端SPA框架,核心是组件化开发和响应式数据绑定。商城前端说到底就是两个核心命题:用户怎么逛商品、怎么完成购买流程。Vue用组件把首页、商品列表、商品详情、购物车、结算页拆开,每个页面就是一个组件,数据通过Vuex或Pinia在不同组件间共享。这种"页面即组件"的思维,正好对应后端"模块即接口"的思维,前后端分离就这么配合起来了。
最后是MySQL。商城系统的数据结构是所有电商系统的缩略版——用户表、商品表、分类表、购物车表、订单表、订单详情表。MySQL在这套项目里承担的是最实在的工作:存储数据、保持事务一致性、提供索引加速查询。毕设级别的商城,完全不需要上Redis、消息队列、分库分表这些重型武器,MySQL单库单表足够支撑,这才符合"毕业设计的平衡点"。
三样技术合起来,证明的不是"你有多懂高并发",而是"你具备独立开发一个完整Web系统的能力"——从数据库建模到后端接口,再到前端页面,你一个人能打通整条链。这才是答辩老师真正想确认的事。
1.2 和"前后端分离"过招:这题目在答辩中的隐藏考察点
前两年代码能跑、界面能点,答辩基本就过了。但这几年不行了,很多学校开始对毕设提出"不能是简单CRUD"的隐性要求。商城的业务逻辑比论坛、博客复杂一个档次,天然有更多可以追问的点。
我梳理过答辩现场最常见的几个问题,几乎是每一届都会被问到的:
- 购物车加入商品后,库存怎么处理?是加车就扣减,还是下单才扣减?
- 用户并发提交订单,库存超卖怎么解决?
- 前端Vue的路由守卫是怎么实现"未登录不能下单"的?
- MySQL的索引是怎么建的?为什么商品名搜索要用那种字段类型?
- 订单表的status字段有哪些值?每个值对应什么业务流程?
这些问题全都能直接映射到代码里。比如"库存扣减时机",你只要在代码里用的是下单时校验并扣减库存,并且通过数据库行锁或者乐观锁来控制并发,就能给出一个清晰的回答。而如果你连库存扣减都没有做,只是"下单生成订单",那答辩时就会很尴尬。
还有一个隐藏考察点:工程化能力。老师会看你有没有用Git、有没有写接口文档、有没有做统一异常处理、有没有在pom.xml里规范依赖版本。这些细节虽然不起眼,但恰恰能区分"下载别人代码跑通了"和"自己真做过一遍"。
1.3 为什么是"米家商城"而非"XX商城":业务复杂度与毕设的平衡点
做毕设最怕两件事:一是功能太少没东西写,二是功能太多做不完。商城系统的魅力就在于它的业务层次是渐进的。
基础版商城:用户注册登录、商品展示、加入购物车、提交订单。这套流程你只需要五张表、十来个接口、七八个Vue页面就能跑通。
进阶版商城:在基础版上增加商品分类、商品搜索、订单状态流转(待支付、已支付、已发货、已完成)、后台管理(商品上下架、订单发货)、图片上传。这些功能单个拆开都不难,合在一起就构成了一个"完整系统"。
扩展版商城:引入支付模拟、优惠券、轮播图管理、数据统计(用ECharts画个简单图表)。这些属于锦上添花,但写到论文里非常加分。
"米家商城"这个名字本身不承担任何特殊技术负担,它给你留了空间。我见过有人硬要做"仿淘宝"的,结果光一个SKU(规格组合)就卡了半个月。而"米家商城"通常是单规格商品,也就是一个商品只有一个价格和一份库存,这在毕设层面完全说得通。你要真想做点差异化,加一个"多规格商品"功能就足够惊艳老师了,没必要在一棵树上吊死。
所以我的判断很直接:这个选题是优质选项,但前提是你得把它当成一个真实项目来做,而不是当成一堆文件来抄。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构与技术栈怎么搭:模块划分、数据流和关键依赖
代码怎么写其实不是最难的,难的是"脑袋里先有一张完整的地图"。你要知道哪个类管什么、哪个接口返回什么、哪个页面调哪个接口。我一般建议先把架构图在脑子里画清楚,再动手写代码。
2.1 后端SpringBoot的模块划分:Controller-Service-Mapper三层怎么落地
后端目录结构我推荐按业务模块分包,而不是按技术分层分包。什么意思?对比一下:
过去很多人喜欢这么分:
code复制com.mall
├── controller
│ └── UserController.java, ProductController.java...
├── service
├── mapper
├── entity
这种分包的好处是结构统一,但缺点是当模块变多时,找代码要来回跳包。我更推荐按业务域分:
code复制com.mall
├── common(统一返回结果、异常处理、统一常量)
├── user(UserController, UserService, UserMapper, User.java)
├── product(ProductController, ProductService, ProductMapper, Product.java)
├── cart(购物车模块)
├── order(订单模块,含订单详情)
├── admin(后台管理相关接口)
└── config(跨域配置、文件上传配置、JWT配置等)
每个业务域独立闭环,改动某个模块的时候,不需要跑到别人的包里去翻代码。对毕设来说,这种结构写进论文的"系统设计"章节也更好解释。
Controller层只做三件事:接收参数、校验参数、调用Service并返回结果。统一返回格式是必须的,我一般使用一个Result类:
java复制public class Result<T> {
private Integer code; // 200成功 500失败 401未登录
private String msg;
private T data;
// 静态方法 success/error
}
这样前端axios拦截器只需要判断code,就能统一处理错误提示,不用每个接口都单独判断。
Service层是业务逻辑的主体,事务注解@Transactional基本都用在这一层。Mapper层用MyBatis-Plus,单表CRUD完全不用写SQL,多表查询再手写XML。
2.2 前端Vue的页面与状态设计:路由、axios封装和组件通信
前端我用Vue 2 + Element UI还是Vue 3 + Element Plus?毕设角度我推荐Vue 2,不是因为技术旧,而是因为网上的教程、博客、踩坑记录最多,毕业设计期间遇到奇怪问题,搜半天就能找到答案。Vue 3虽然更现代,但Element Plus的版本更新很快,有些组件用法有变动,反而容易卡住。
前端页面规划上,用户端路由大致如下:
/首页(商品推荐列表)/product/:id商品详情/search?keyword=xx搜索结果,也是商品列表页/cart购物车/checkout结算页/order/list订单列表/order/detail/:id订单详情/login、/register
管理端路由单独一个布局:
/admin/dashboard后台首页/admin/product商品管理/admin/order订单管理/admin/user用户管理
axios封装是前端的关键点。我会在src/utils/request.js里做一个实例,设置baseURL,再加请求拦截器和响应拦截器。请求拦截器从localStorage取token并放到请求头:config.headers.token = token。响应拦截器统一处理code不为200的情况,特别是401时跳转登录页。
登录状态管理用Vuex或Pinia存一份用户信息和token,页面刷新后通过router.beforeEach守卫去做检查:如果要去购物车、结算、订单这类需要登录的页面,就判断Vuex里有没有用户信息,没有就跳转登录页并带上redirect参数。这个逻辑写起来不难,但在答辩时一定要能讲清楚,因为它体现了你对"前端Route权限控制"的理解。
2.3 MySQL数据表设计:用户、商品、订单、购物车这四张核心表的字段细节
数据库设计是整个毕设的灵魂,因为论文里最长的表往往就是数据表设计。我直接给出核心表的字段思路,都是经过验证的版本。
用户表:
sql复制CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(255) NOT NULL COMMENT '密码(MD5/Bcrypt加密)',
`nickname` varchar(50) DEFAULT NULL,
`avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
`phone` varchar(20) DEFAULT NULL,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
商品表:
sql复制CREATE TABLE `product` (
`id` int NOT NULL AUTO_INCREMENT,
`category_id` int DEFAULT NULL COMMENT '分类ID',
`name` varchar(100) NOT NULL COMMENT '商品名称',
`sub_title` varchar(200) DEFAULT NULL COMMENT '副标题',
`main_image` varchar(255) DEFAULT NULL COMMENT '主图URL',
`price` decimal(10,2) NOT NULL COMMENT '价格',
`stock` int NOT NULL DEFAULT 0 COMMENT '库存',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '1上架 0下架',
`detail` text COMMENT '商品详情',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
注意price一定要用decimal(10,2),千万别用float或double,浮点类型算金额会出精度问题,答辩时被问到"多少钱"很容易暴露。
购物车表:
sql复制CREATE TABLE `cart_item` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`product_id` int NOT NULL,
`quantity` int NOT NULL DEFAULT 1,
`checked` tinyint NOT NULL DEFAULT 1 COMMENT '是否勾选',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_product` (`user_id`,`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
对同一个用户加同一个商品,我会用唯一键保证一条记录,然后通过ON DUPLICATE KEY UPDATE quantity = quantity + 1或者在代码里先查后改,避免购物车出现同一商品两条数据。
订单表与订单详情表需要分开设计,这是"订单主表+订单明细表"的标准模型:
sql复制CREATE TABLE `orders` (
`id` int NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL COMMENT '订单号',
`user_id` int NOT NULL,
`total_price` decimal(10,2) NOT NULL,
`status` tinyint NOT NULL DEFAULT 0 COMMENT '0待支付 1已支付 2已发货 3已完成 4已取消',
`receiver_name` varchar(50) NOT NULL,
`receiver_phone` varchar(20) NOT NULL,
`receiver_address` varchar(200) NOT NULL,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`pay_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `order_item` (
`id` int NOT NULL AUTO_INCREMENT,
`order_id` int NOT NULL,
`product_id` int NOT NULL,
`product_name` varchar(100) NOT NULL,
`product_image` varchar(255) DEFAULT NULL,
`price` decimal(10,2) NOT NULL,
`quantity` int NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
订单表里冗余了收件人信息,并且订单详情表把商品快照存在了字段里,这两点都要能跟老师讲清楚:收件人信息必须冗余,因为订单生成后地址变了不能影响历史订单;商品名称和价格也属于快照数据,因为商品下架或改名了,历史订单依然要能展示当时的信息。
2.4 数据流串联:从"用户下单"到"库存扣减"的一次完整请求旅程
我习惯用文字把这个流程顺一遍,顺完整个项目就通了。以用户购买一件商品为例:
前端用户在商品详情页点击"立即购买",Vue组件调用POST /api/order/create,请求体是{productId: 3, quantity: 1, receiverName: "张三", receiverPhone: "138...", receiverAddress: "..."}。请求经过axios拦截器,把token放到header里,然后到达后端Controller。
Controller把参数封装成DTO,调用OrderService的createOrder方法。Service层第一步从token中解析出userId,然后查商品信息,校验商品是否存在、是否上架、库存是否充足。接着生成订单号,订单号的生成我推荐yyyyMMddHHmmss + 4位随机数或者用UUID去掉横线。
然后保存订单头和订单明细。这里要加@Transactional,因为涉及"插入订单表、插入订单明细表、扣减库存"三个写操作,任何一个失败,整个订单都不应该存在。
在这个流程里有一个容易忽略的地方:库存扣减的SQL不能写成UPDATE product SET stock = stock - 1 WHERE id = ?这么朴素,而是要带上AND stock >= ?条件,比如:
sql复制UPDATE product SET stock = stock - #{quantity}
WHERE id = #{productId} AND stock >= #{quantity}
然后通过int rows = productMapper.updateStock(...)的返回值判断扣减是否成功。rows=0说明库存不足,直接抛异常回滚。这是最简单、完全够用的并发防超卖方案,也是答辩时最能讲清楚的方案。
这个请求的终点是后端返回Result.success(orderNo),前端拿到订单号跳转订单详情页。一条完整的链路清晰了,后面加支付模拟、加物流状态,全都是在这条链上做扩展。
3. 核心功能实现时最容易翻车的技术点
理论说完了,说点能直接落地的。商城项目里有几个技术点几乎人人都会遇到,但也是翻车重灾区,我一个个过。
3.1 登录鉴权:JWT+拦截器还是Spring Security
这两个方案我都试过。Spring Security功能强大但学习曲线陡峭,对毕设来说它的UserDetailsService、过滤器链概念会让很多人卡到崩溃。我强烈建议用JWT+拦截器的方式,理由只有一个:简单、可控、讲得清。
流程是这样的:用户登录成功后,后端用JWT工具类生成一个token,里面包含userId和username,设置过期时间比如7天。返回给前端,前端存到localStorage。后续每个需要登录的接口,前端在header里带token。后端写一个LoginInterceptor,继承HandlerInterceptorAdapter(或者实现HandlerInterceptor),在preHandle里取header里的token,解析成功放行,失败返回401。
一个关键细节:拦截器要排除登录、注册、商品查询这些公开接口。常见做法是在WebMvcConfigurer里注册拦截器,并设置excludePathPatterns:
java复制registry.addInterceptor(loginInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/user/login", "/api/user/register", "/api/product/**");
JWT本身是自包含的,不用在Redis里存session,天然适合前后端分离,也能跟老师解释什么叫"无状态认证"。另外注意密码不能明文存,用BCrypt或MD5加盐都行,BCrypt更稳妥,Spring Security里就有BCryptPasswordEncoder,单独引这个类就行。
3.2 商品搜索与分页:MyBatis-Plus的LambdaQueryWrapper怎么用
商品列表页和搜索页本质是同一个接口:GET /api/product/list?keyword=&categoryId=&pageNum=1&pageSize=8。后端返回一个分页对象,包括记录列表、总条数、总页数、当前页。
MyBatis-Plus的Page和LambdaQueryWrapper组合起来写这个接口极其爽快:
java复制Page<Product> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.like(Product::getName, keyword);
}
if (categoryId != null) {
wrapper.eq(Product::getCategoryId, categoryId);
}
wrapper.eq(Product::getStatus, 1); // 只查上架商品
wrapper.orderByDesc(Product::getCreateTime);
productMapper.selectPage(page, wrapper);
这段代码有几个值得在答辩里吹的点:用Lambda表达式避免了字段名拼错的问题;Page对象免去手写LIMIT和COUNT语句;条件判断通过if动态拼接,避免了SQL注入。
前端拿到分页数据后用Element UI的el-pagination组件展示页码,每次切换页码重新请求接口。搜索框防抖是前端的小优化,输入停止300ms后再发起请求,这个能写进论文的"系统优化"里。
3.3 购物车与订单:事务边界和库存扣减的并发控制
我前面提过库存扣减的SQL带条件写法,这里再扩展一下事务的边界问题。后端下单接口的事务切点放在Service层的createOrder方法上,里面包住:查商品、扣库存、生成订单头、生成订单明细、清空购物车中对应商品。五个操作要么全部成功,要么全部回滚。
这里有个经典型的理解题:为什么不能把事务加在Controller层?因为Controller的主要职责是参数接收和结果返回,事务需要作用于业务方法内部的所有数据库操作,作用在Service方法上是标准做法。老师如果问了,你答"事务放在Controller会导致事务范围过大,连接持有时间过长,影响数据库连接池使用效率"会非常加分。
购物车批量下单的逻辑也容易出问题:前端勾选了三个商品点击结算,后端接口接收的是一个List<CartItemDTO>,在事务里循环处理每个商品。此时某个商品库存不足,整个事务回滚,所有商品都不能下单。这个"全有或全无"的逻辑必须跟用户提示清楚,前端也要先做一次库存预检查。
3.4 图片上传与访问:本地存储还是OSS,反向代理和虚拟路径的处理
毕设项目传图片,我不建议一上来就接阿里云OSS——需要实名、需要配置Bucket、需要写SDK代码,虽然不难但会分散精力。本地存储完全够用。
后台上传接口:POST /api/admin/product/upload接收MultipartFile,保存到服务器的一个静态目录,比如项目的/upload/文件夹,文件名用时间戳加随机数重新生成,防止重名:
java复制String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
File dest = new File(uploadDir + fileName);
file.transferTo(dest);
然后给前端返回一个可访问的URL。这里有一个大坑:直接返回/upload/xxx.jpg,浏览器是访问不到的,因为SpringBoot默认只映射static/下的静态资源,/upload/不在其中。解决方法有两个:
一是把本地上传目录配置成Jackson静态资源映射:
java复制registry.addResourceHandler("/upload/**")
.addResourceLocation("file:" + uploadDir + "/");
这样请求/upload/1.jpg就能映射到磁盘上的实际文件。
二是部署时通过Nginx做映射:location /upload/ { alias /www/mall/upload/; }。我建议本地开发用第一种,部署后用第二种,论文里两种方案都能提,展示你对"静态资源访问"有充分理解。
另一个小技巧是上传文件的后缀白名单验证,比如只允许jpg、png、gif、webp。不要只依赖前端类型判断,后端一定要再判断一次扩展名,这是安全的底线。前端再用Element Upload组件做图片预览和提交。
4. 论文写作的素材组织:从功能实现到答辩底稿
很多人的代码写完了,论文却不知道怎么写,或者写得跟流水账一样。我提供一个直接能用的目录骨架:
- 绪论:背景与意义、国内外研究现状、论文组织结构
- 相关技术介绍:SpringBoot、Vue、MySQL、MyBatis-Plus
- 系统分析:可行性分析、需求分析(功能需求、非功能需求)、用例图
- 系统设计:总体架构图、功能模块设计、数据库设计(ER图、表结构)
- 系统实现:每个核心模块的实现思路、关键代码、界面截图
- 系统测试:测试环境、功能测试用例、测试结果
- 总结与展望
这套目录跟绝大多数学校的毕设模板都能对接,不要自己发明奇怪的结构。关键在于每个章节的内容要有"证据"支撑,而不是套话。
4.1 论文目录怎么定:需求分析、总体设计、详细设计、测试四块怎么分配
需求分析部分,把系统的角色和功能用表格列出来就行。比如用户角色有浏览商品、搜索商品、管理购物车、下单、查看订单;管理员角色有商品管理、分类管理、订单管理、用户管理。每一条对应一个用例描述,一个用例三五行字,不需要写太深,重点是覆盖全面。
总体设计部分,放一张架构图(前端Vue+Nginx、后端SpringBoot+MyBatis-Plus、数据库MySQL),再放一张功能模块图。这两张图用Visio或draw.io画都行,画得清晰大方即可,不用追求花哨。
详细设计部分,不要写代码全文,而是挑核心模块讲。一般挑三个:登录鉴权模块、商品管理模块、订单管理模块。每个模块配一段伪代码或核心代码片段加解释,再配一个界面截图或者接口调用时序。这里最忌讳把全部代码贴进来,页数上去了但显得很水。重点是"通过这段代码达到了什么目的"。
4.2 数据库设计文档怎么写:ER图、表结构说明和数据字典
数据库设计章节是评审老师最爱翻的部分之一。你要给出完整ER图,以及每张表的字段说明。我建议用表格形式画数据字典,例如商品表:
| 字段名 | 类型 | 允许为空 | 主键/索引 | 说明 |
|---|---|---|---|---|
| id | int | 否 | 主键 | 主键ID |
| category_id | int | 是 | 普通索引 | 分类ID |
| name | varchar(100) | 否 | 普通索引 | 商品名称 |
| price | decimal(10,2) | 否 | 无 | 商品价格 |
| stock | int | 否 | 无 | 库存数量 |
| status | tinyint | 否 | 无 | 上下架状态 |
每张表一个这样的表格,写完大概五六页,已经能撑起数据库设计整个章节了。ER图中注意体现表间关系:分类表1对多商品表,用户表1对多订单表,订单表1对多订单详情表,用户表1对多购物车表。这些关系正好能画出完整的ER图。
4.3 测试章节怎么编才可信:功能测试用例表和性能测试的替代方案
测试章节写到"测试通过"是不够的,要有测试用例表。我给个模板:
| 用例编号 | 测试名称 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|
| TC01 | 用户注册 | 输入用户名密码及手机号,点击注册 | 注册成功并跳转登录页 | 与预期一致 |
| TC02 | 用户登录 | 输入正确账号密码 | 登录成功返回token | 与预期一致 |
| TC03 | 加入购物车 | 商品详情页点击加入购物车 | 购物车数量+1 | 与预期一致 |
| TC04 | 提交订单 | 勾选商品点击结算 | 生成订单,库存减扣 | 与预期一致 |
| TC05 | 库存不足 | 设置库存为1,购买2件 | 提示库存不足,下单失败 | 与预期一致 |
性能测试不好做的话,可以不硬编QPS报告,而是写"使用JMeter对登录接口进行并发100次压测,系统平均响应时间XXms,无异常"。这个数据哪怕是用Postman循环跑出来的也行,但如果你没真跑,建议写的时候描述得严谨一点。
5. 部署流程实录:从IntelliJ IDEA到云服务器的完整命令
部署是拿到源码后最容易卡住的一环。我把一套亲测可用的流程写下来,照着走基本能通。
5.1 本地跑通:MySQL初始化、后端启动参数和前端npm run build
第一步,本地装MySQL 8.0。Windows下安装时有一步会让你选认证方式,务必选"Use Legacy Authentication Method"或者安装完执行一条SQL改认证插件,否则SpringBoot连接会报Public Key Retrieval is not allowed错误。这个坑我后面细讲。
第二步,创建数据库。打开Navicat或命令行执行:
sql复制CREATE DATABASE mall CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE mall;
SOURCE mall.sql;
导入项目里的SQL文件后,确认表都建好了。
第三步,配置后端application.yml:
yaml复制server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
尤其serverTimezone=Asia/Shanghai必须有,不然MySQL 8会报时区错误。
第四步,IDEA里启动SpringBoot应用。控制台出现Started Application in x.xxx seconds表示后端OK。用Postman或浏览器访问http://localhost:8080/api/product/list,能返回JSON就通了。
第五步,前端打开项目目录,命令行执行:
bash复制npm install
npm run serve
浏览器访问http://localhost:8081。这里要注意前端和后端端口不一样,前端开发服务器默认8080会和后端冲突,我用Vue CLI配置里把devServer端口改成了8081,同时配置代理proxy把/api转发到http://localhost:8080。这一步解决了开发环境下的跨域问题。
5.2 Linux服务器部署:打包上传、jar包启动、nginx反向代理
本地跑通后,部署到云服务器(CentOS 7/Ubuntu Server都行)的核心步骤是这些:
后端打包。把application.yml里的数据库地址改成服务器地址,然后执行:
bash复制mvn clean package -DskipTests
生成target/mall-0.0.1-SNAPSHOT.jar。上传到服务器/www/mall/目录。
前端打包:
bash复制npm run build
生成dist/目录,上传到服务器/www/mall/dist/。
服务器上装好JDK 1.8及以上和MySQL,启动后端:
bash复制nohup java -jar mall-0.0.1-SNAPSHOT.jar --server.port=8080 > /www/mall/log.log 2>&1 &
注意日志输出和后台运行,nohup和&缺一不可。
然后装Nginx,配置反向代理:
nginx复制server {
listen 80;
server_name your_domain_or_ip;
root /www/mall/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /upload/ {
alias /www/mall/upload/;
}
}
这里有三处关键:try_files避免前端路由刷新404;/api/代理到后端Java服务;/upload/映射上传图片目录。配完执行nginx -s reload就行。
5.3 部署文档的写作要点:给别人能照着复现的版本
部署文档要从"一个手里只有这套源码的新手"的视角来写,别默认对方什么都会。我建议至少包含:环境版本要求(JDK版本、MySQL版本、Node版本、Nginx版本)、每一步的命令和截图、可能出现的错误和解决方式。一份好的部署文档在展示时给老师看,专业感会明显提升。
6. 你会踩到的坑和我给的避坑建议
这些坑我几乎每次帮人看毕设都能碰到,单独列一节,希望能帮你省一个周末。
6.1 MySQL 8的时区与SSL连接报错
第一个坑是The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized,中文乱码样式的报错,原因就是MySQL驱动和系统时区对不上。解决方案就是连接串加serverTimezone=Asia/Shanghai。
第二个坑是Public Key Retrieval is not allowed,这是MySQL 8的caching_sha2_password认证插件导致的。解决方式有三种:连接串加allowPublicKeyRetrieval=true;把用户认证方式改回mysql_native_password;或者建用户时指定认证插件。毕设项目最省事的就是连接串上带参数。
6.2 Vue前端跨域与axios baseURL的配置矛盾
本地开发时前端8081访问后端8080,跨域。最省心的方案是Vue CLI的proxy代理,在vue.config.js里配置:
js复制module.exports = {
devServer: {
port: 8081,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
}
然后把axios的baseURL设为'/api',这样开发环境走代理,生产环境走Nginx反向代理,前端代码不需要改环境。很多人的坑在于:开发时axios写死了http://localhost:8080/api,打包上线后还在请求8080,导致服务器上怎么都连不上后端。正确做法是开发和生产用同一个/api前缀,让代理层来转发。
6.3 SpringBoot版本太高导致的依赖冲突
很多人在网上找的项目是SpringBoot 2.x,自己新建项目时IDEA默认拉SpringBoot 3.x,然后Mapper、JWT等依赖全部报错。SpringBoot 3要求JDK 17,且javax.servlet换成了jakarta.servlet,很多老代码跑不起来。
我建议如果跟着教程做,就锁版本。在pom.xml里明确指定:
xml复制<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
这是2.x的最后一个版本,稳定,且JDK 8也能跑。等毕设做完再谈升级新技术,不要在关键时期给自己加难度。
6.4 答辩前的检查清单
最后给一份答辩前的检查清单,都是我见过真实翻车场景总结出来的:
- 确认数据库SQL能正常导入,不要只在自己电脑上能跑
- 确认前端所有页面在打包后(dist方式)正常显示,不要只开发模式OK
- 确认上传图片在服务器上能访问,不要只本地路径OK
- 准备几个核心接口的调用过程,能在屏幕上现场演示下单流程
- 准备一张清晰的架构图和ER图,答辩PPT里用
- 把项目里每个模块的"为什么"想清楚,不要只记"是什么"
这三个部署和答辩的坑,花费的时间比写代码还多,但恰恰是这些细节让整个项目立得住。
按照这套思路做下来,不止是拿到一个能运行的商城,而是把SpringBoot、Vue、MySQL这三大件的知识真正串成了一条线。以后面试聊项目,也能讲清楚"我做了什么、为什么这么做、遇到什么问题、怎么解决的"。这比单纯拥有一份源码重要得多。
