
简介这是一套面向计算机相关专业学生与开发者的高分成品毕业设计资源主题为家具销售电商平台采用Vue.js前端、SpringBoot后端与MySQL数据库构建适合用作毕设、课程设计、作业或项目立项演示。系统按管理员、用户与前台三类角色划分功能涵盖家具分类、热销与折扣家具、订单评价、收藏、公告资讯及客服等模块业务链路完整。压缩包共843个文件约24.9MB以145个Java源码、52个Vue组件、153个JavaScript脚本、46个HTML页面与44个CSS样式为主体另含SQL建库脚本、XML配置、图片与字体等静态资源并附论文、说明文档与数据库文件。目前已有56人学习关注。代码经测试可正常运行读者可据此掌握前后端分离架构、权限控制与订单流程实现也能在现有结构上二次开发扩展功能遇到运行问题还可私聊获取远程教学支持。1. 家具电商毕设选型为什么 Vue.js SpringBoot MySQL 是稳妥组合家具销售电商平台这个题目几乎每年毕业季都会被翻出来做一遍。原因很直接业务场景完整商品、分类、购物车、订单、用户、后台管理一条龙答辩时老师能问的点多自己也能讲清楚。但真正动手时很多人卡在第一步——技术栈怎么定。我见过太多同学一上来就想上微服务、上 Redis 集群、上消息队列结果环境还没跑通时间已经过去一半。基于 Vue.js SpringBoot MySQL 这套组合是当前计算机毕业设计里最稳的落地路径。Vue.js 负责前端交互组件化开发让商品列表、购物车、订单页可以拆开写SpringBoot 负责后端接口自动配置和内嵌 Tomcat 让项目能独立运行MySQL 存商品、用户、订单这些核心数据。三者配合前后端分离职责清晰调试也方便。这套方案适合两类人一是想稳扎稳打拿高分、不想在环境上翻车的同学二是想借这个项目把前后端分离开发流程真正走一遍的人。后面我会按实际开发顺序把每一步的命令、参数和踩坑点讲透。2. 环境搭建与项目骨架把 Vue.js 和 SpringBoot 跑起来2.1 前端 Vue.js 环境与项目初始化Vue.js 的版本选择直接决定后面代码怎么写。目前主流是 Vue 3配合 Vite 构建启动快、热更新稳。如果你学校机房或自己电脑上 Node.js 版本较老建议先升级到 16 以上否则 Vite 会报错。安装命令如下# 检查 Node.js 版本建议 16.0 以上 node -v # 全局安装 Vue CLI如果使用 Vue 2 可选 npm install -g vue/cli # 使用 Vite 创建 Vue 3 项目项目名设为 furniture-mall npm create vitelatest furniture-mall -- --template vue # 进入项目目录并安装依赖 cd furniture-mall npm install # 启动开发服务器默认端口 5173 npm run dev这段命令的逻辑是先用node -v确认环境避免版本过低导致 Vite 启动失败然后通过npm create vite拉取模板模板里已经配好了 Vue 3 的基本结构npm install安装 axios、vue-router、pinia 这些后续要用的库最后npm run dev启动本地服务。参数上项目名furniture-mall可以换成你喜欢的名字但建议用英文小写加连字符避免路径问题。启动后浏览器访问http://localhost:5173能看到 Vue 默认页面就说明前端环境通了。接下来装路由和状态管理家具电商平台至少需要商品列表、详情、购物车、订单、登录注册这几个页面# 安装 vue-router 和 pinia npm install vue-router4 pinia axios # 安装 element-plus 组件库后台表格和表单用得上 npm install element-plusvue-router4是 Vue 3 配套版本pinia替代 Vuex 做状态管理购物车数据跨页面共享就靠它。axios负责发请求element-plus提供现成的表格、分页、弹窗组件能省掉大量写 CSS 的时间。这些库的版本不用刻意锁死用npm install默认拉取的最新稳定版即可但要注意element-plus和 Vue 3 的兼容性如果启动报错优先检查这两个的版本匹配。2.2 后端 SpringBoot 项目结构与依赖配置SpringBoot 这边我一般用 Spring Initializr 生成骨架也可以直接在 IDE 里新建。关键点是依赖要一次选对不然后面缺什么补什么容易乱。一个家具电商平台后端至少需要这些!-- pom.xml 核心依赖 -- dependencies !-- Web 模块提供 REST 接口 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Plus简化 MySQL 操作 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Lombok减少 getter/setter 代码 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies这里选 MyBatis-Plus 而不是 JPA是因为家具电商的查询条件多——按分类筛、按价格排序、按名称模糊搜MyBatis-Plus 的QueryWrapper写起来更顺手也更容易控制 SQL。MySQL 驱动用mysql-connector-j这是 MySQL 8.0 之后的官方驱动名如果你本地装的是 MySQL 5.7驱动类名要改成com.mysql.jdbc.Driver否则启动会报找不到驱动。Lombok 不是必须但用了之后实体类不用手写 getter/setter代码量少一半。配置文件application.yml里数据库连接是第一个要改的地方server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/furniture_mall?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplserverTimezoneAsia/Shanghai这个参数必须加否则 MySQL 8 会报时区错误。map-underscore-to-camel-case让数据库的create_time自动映射到 Java 的createTime省去手动配 ResultMap。log-impl打开 SQL 日志调试时能看到实际执行的语句答辩演示时也能证明数据是真的从数据库查出来的。2.3 数据库建表与前后端联调最小闭环数据库先建库再建表家具电商平台核心表不多但字段要设计合理CREATE DATABASE furniture_mall DEFAULT CHARACTER SET utf8mb4; USE furniture_mall; -- 商品表 CREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, category_id BIGINT, price DECIMAL(10,2), stock INT DEFAULT 0, image_url VARCHAR(255), description TEXT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 用户表 CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单表 CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, total_amount DECIMAL(10,2), status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );utf8mb4而不是utf8因为家具名称里可能有特殊符号utf8mb4支持更全。订单表名用orders而不是order因为order是 SQL 关键字直接写会报语法错误这是很多人建表时踩的第一个坑。status用 TINYINT 存 0/1/2 表示待付款、已付款、已发货比存字符串省空间也快。建完表写一个测试接口验证前后端能通RestController RequestMapping(/api/product) public class ProductController { Autowired private ProductService productService; GetMapping(/list) public Result list() { // 返回所有商品实际项目要加分页 return Result.success(productService.list()); } }前端在src/api/product.js里调这个接口import axios from axios const request axios.create({ baseURL: http://localhost:8080/api, timeout: 5000 }) export const getProductList () request.get(/product/list)baseURL指向后端 8080 端口前端 5173 端口访问 8080 会跨域需要在 SpringBoot 里加CrossOrigin注解或者配一个全局跨域配置。这一步跑通浏览器能看到商品 JSON 数据整个项目骨架就算立住了。3. 核心业务实现商品、购物车与订单的代码落地3.1 商品列表分页与条件查询家具电商的商品列表不能一次性全查出来必须分页。MyBatis-Plus 的分页插件要先配置Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }然后 Controller 里接收分页参数和筛选条件GetMapping(/page) public Result page(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String name, RequestParam(required false) Long categoryId) { PageProduct page new Page(pageNum, pageSize); QueryWrapperProduct wrapper new QueryWrapper(); if (StringUtils.hasText(name)) { wrapper.like(name, name); } if (categoryId ! null) { wrapper.eq(category_id, categoryId); } wrapper.orderByDesc(create_time); return Result.success(productService.page(page, wrapper)); }pageNum和pageSize给默认值前端不传也能正常返回。like对应 SQL 的LIKE %name%eq对应orderByDesc按创建时间倒序新商品排前面。这里有个细节categoryId用Long而不是long因为前端可能不传这个参数用包装类型才能判断null。如果写成基本类型不传参时默认是 0会查出错误结果。前端表格用 element-plus 的el-table和el-pagination配合template el-table :datatableData border el-table-column propname label商品名称 / el-table-column propprice label价格 / el-table-column propstock label库存 / /el-table el-pagination current-changehandlePageChange :current-pagepageNum :page-sizepageSize :totaltotal layoutprev, pager, next / /templatehandlePageChange里重新调接口把新的pageNum传过去。这套分页逻辑是电商平台最基础也最常用的答辩时老师大概率会问“数据量大了怎么办”你可以答“分页查询 索引优化”category_id和create_time上建索引就能明显提速。3.2 购物车数据结构与前端状态管理购物车用 Pinia 管理核心是存一个数组每项包含商品 ID、名称、价格、数量、选中状态// stores/cart.js import { defineStore } from pinia export const useCartStore defineStore(cart, { state: () ({ items: [] }), getters: { totalCount: (state) state.items.reduce((sum, item) sum item.quantity, 0), totalPrice: (state) state.items .filter(item item.checked) .reduce((sum, item) sum item.price * item.quantity, 0) }, actions: { addToCart(product) { const existing this.items.find(item item.id product.id) if (existing) { existing.quantity } else { this.items.push({ ...product, quantity: 1, checked: true }) } }, removeFromCart(id) { this.items this.items.filter(item item.id ! id) } } })totalPrice只累加checked为 true 的商品这对应购物车里勾选结算的逻辑。addToCart先查是否已存在存在就数量加一不存在才 push 新项避免同一商品重复出现。Pinia 的数据存在内存里刷新页面会丢所以实际项目里还要配合localStorage做持久化在addToCart之后把this.items存进去初始化时再读出来。后端购物车表可以简化因为毕设通常不要求多端同步购物车数据放前端就够了。但如果老师要求购物车也要存数据库那就加一张cart表字段包括user_id、product_id、quantity接口按用户 ID 增删改查。3.3 订单创建与库存扣减的事务处理下单是电商平台最核心也最容易出 bug 的地方。用户点“提交订单”后后端要做三件事生成订单记录、扣减商品库存、清空购物车。这三步必须在一个事务里否则可能出现订单生成了但库存没扣或者库存扣了订单没生成。Service public class OrderService { Autowired private OrderMapper orderMapper; Autowired private ProductMapper productMapper; Transactional(rollbackFor Exception.class) public Result createOrder(Long userId, ListOrderItem items) { // 1. 计算总价并校验库存 BigDecimal total BigDecimal.ZERO; for (OrderItem item : items) { Product product productMapper.selectById(item.getProductId()); if (product.getStock() item.getQuantity()) { throw new RuntimeException(库存不足 product.getName()); } total total.add(product.getPrice().multiply(new BigDecimal(item.getQuantity()))); } // 2. 扣减库存 for (OrderItem item : items) { productMapper.decreaseStock(item.getProductId(), item.getQuantity()); } // 3. 生成订单 Order order new Order(); order.setUserId(userId); order.setTotalAmount(total); order.setStatus(0); orderMapper.insert(order); return Result.success(order.getId()); } }Transactional(rollbackFor Exception.class)是关键默认 Spring 只回滚RuntimeException加上rollbackFor后所有异常都回滚。库存扣减用自定义 SQLUPDATE product SET stock stock - #{quantity} WHERE id #{id} AND stock #{quantity}AND stock #{quantity}是乐观锁思路防止并发超卖。如果返回影响行数为 0说明库存不够抛异常回滚。这里有个血泪经验订单表插入后要返回主键 ID 给前端MyBatis-Plus 的insert默认会把自增 ID 回填到实体对象里直接order.getId()就能拿到。但如果你用的是自定义 XML 的insert要在insert标签上加useGeneratedKeystrue keyPropertyid否则拿到的 ID 是 null前端跳转订单详情页就会 404。4. 避坑与排查家具电商毕设最常见的 5 个翻车点4.1 跨域请求被浏览器拦截现象前端调后端接口浏览器控制台报Access to XMLHttpRequest at http://localhost:8080/api/product/list from origin http://localhost:5173 has been blocked by CORS policy。原因前端 5173 端口和后端 8080 端口不同源浏览器默认禁止跨域请求。解决在 SpringBoot 里加全局跨域配置不要只在单个 Controller 上加CrossOrigin因为接口多了会漏。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }allowedOriginPatterns(*)比allowedOrigins(*)更安全后者在allowCredentials(true)时会报错。maxAge(3600)让预检请求缓存一小时减少重复 OPTIONS 请求。4.2 MySQL 8 时区与驱动类名不匹配现象SpringBoot 启动报The server time zone value йʱ is unrecognized或Cannot load driver class: com.mysql.jdbc.Driver。原因MySQL 8 的驱动类名是com.mysql.cj.jdbc.Driver不是 5.x 的com.mysql.jdbc.Driver时区不配会按系统默认读中文系统下会乱码。解决application.yml里驱动类名改成com.mysql.cj.jdbc.DriverURL 后面加serverTimezoneAsia/Shanghai。如果还报错检查pom.xml里 MySQL 驱动版本8.0 以上用mysql-connector-j5.7 用mysql-connector-java。4.3 Vue 打包后放 SpringBoot 里刷新 404现象npm run build生成的dist文件夹放进 SpringBoot 的static目录首页能访问但刷新/product这种路由就 404。原因Vue Router 默认是 history 模式刷新时浏览器直接向服务器请求/productSpringBoot 找不到这个路径对应的资源。解决两种方案。一是 Vue Router 改成 hash 模式URL 带#刷新不会 404但不太好看。二是 SpringBoot 加一个转发配置把所有非 API 请求转发到index.htmlController public class WebConfig { RequestMapping(value /{path:[^\\.]*}) public String forward() { return forward:/index.html; } }这个正则[^\\.]*表示不包含点的路径API 请求带/api前缀不会被拦截静态资源如.js、.css带点也不会被拦截。4.4 订单库存扣减出现负数现象并发测试时两个请求同时下单库存从 1 变成 -1。原因先查库存再扣减两步之间有时间差两个线程都查到库存为 1都执行扣减。解决用UPDATE product SET stock stock - #{quantity} WHERE id #{id} AND stock #{quantity}把判断和扣减合并成一条 SQL利用数据库行锁保证原子性。执行后检查返回的影响行数为 0 就抛异常回滚。这是最轻量的解决方案不需要引入分布式锁。4.5 前端 axios 请求后端返回 401 但没登录现象调接口返回 401但用户明明已经登录了。原因登录后 token 存在了 Pinia 里但 axios 请求头没带上 token后端拦截器校验失败。解决在 axios 拦截器里统一加 tokenrequest.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config })后端拦截器从Authorization头里取 token 校验。注意Bearer后面有个空格漏了空格后端解析会失败。token 存localStorage而不是 Pinia因为 Pinia 刷新会丢localStorage持久化更可靠。5. 答辩加分项把毕设从“能跑”推到“能讲”5.1 用 SQL 日志和接口文档证明数据真实性答辩时老师最常问的一句话是“你这个数据是真的从数据库来的吗”。很多人答“是的”但拿不出证据。我的习惯是演示时打开 SpringBoot 控制台MyBatis-Plus 配了log-impl: org.apache.ibatis.logging.stdout.StdOutImpl之后每次查询都会打印实际执行的 SQL 和参数。点一下商品列表控制台输出SELECT id,name,price FROM product WHERE category_id 1 LIMIT 10老师一看就信了。另外花半小时集成 Knife4j 或 Swagger把接口文档自动生成出来。访问http://localhost:8080/doc.html能看到所有接口的入参、出参、在线调试。答辩时老师问“你这个下单接口怎么调的”你直接打开文档点“调试”填参数发请求比口头解释强十倍。依赖加knife4j-openapi2-spring-boot-starter配置文件里开一下knife4j.enabletrue就行成本很低。5.2 数据库索引与慢查询的应对说辞老师如果问“数据量大了怎么办”不要只答“加索引”要具体。家具电商平台最常用的查询是“按分类查商品”和“按名称搜商品”对应的优化是查询场景索引字段SQL 示例分类筛选category_idWHERE category_id 1名称搜索name前缀索引WHERE name LIKE 沙发%订单列表user_id create_timeWHERE user_id 1 ORDER BY create_time DESCLIKE %沙发%这种前后都带百分号的查询用不上索引但毕设数据量小几千条以内全表扫描也很快。如果老师追问就答“实际生产环境会用 Elasticsearch 做全文检索毕设阶段用 MySQL 的 LIKE 满足功能需求”。这个回答既诚实又展示了知识面。5.3 论文里怎么把技术选型写出深度论文的“技术选型”章节不要写成“Vue.js 是一个前端框架SpringBoot 是一个后端框架”这种百度百科式介绍。我的写法是先列需求再对比方案最后说为什么选这个。比如前端框架对比 Vue.js 和 ReactVue.js 上手快、中文文档全、组件化开发适合中小型项目React 生态更大但学习曲线陡毕设周期短选 Vue.js 更务实。后端对比 SpringBoot 和 SSMSpringBoot 自动配置省去大量 XML内嵌 Tomcat 部署方便适合快速开发。MySQL 对比 PostgreSQLMySQL 安装配置简单、资料多、学校机房普遍预装遇到问题好排查。这样写老师看到的是你的决策过程不是复制粘贴的百科词条。每段控制在 200 字左右配一张对比表格论文这一章就能拿不错的分。5.4 一个我踩过的坑别在答辩前夜改依赖版本最后说个血泪教训。我见过一个同学答辩前一天觉得 SpringBoot 版本太低想升到最新结果pom.xml一改MyBatis-Plus 不兼容启动直接报NoSuchMethodError连夜回滚版本折腾到凌晨三点。毕设项目不是生产项目稳定比新潮重要。所有依赖版本在项目初始化时就定好后面不要动。如果非要升级先在另一个分支上试跑通了再合并。答辩前一周只改 bug不加功能不升版本。这个习惯让我这些年少熬了很多夜。希望帮到你。本文还有配套的精品资源点击获取