简介:这是一套基于SpringBoot后端与Vue前端技术栈构建的网上超市系统完整毕业设计源码,面向计算机相关专业本科生、研究生及初入开发领域的学习者,适用于毕业设计、课程设计、项目实训与全栈技术进阶实践。资源包共807个文件,涵盖122个Java后端核心类、151个JavaScript逻辑脚本、40个Vue组件、65个JPG与30个PNG图片资源、162个SVG图标及配套CSS、HTML、SQL等文件,完整呈现前后端分离架构下的用户管理、商品浏览、购物车、订单处理等电商核心功能模块;压缩包大小为26.26MB,结构清晰,含启动脚本(bat)、UI框架样式(elementui/layui/bootstrap)及多格式静态资源。已有55人下载学习,资源经实际运行验证,代码稳定、功能完整,并附有高分答辩文档、系统设计说明与部署指南,可直接用于毕设交付或在此基础上拓展微服务、支付集成等进阶功能。
1. 这不是又一个“超市模板”,而是一套能跑通支付闭环、支持真实库存扣减、经得起答辩追问的 SpringBoot + Vue 网上超市高分毕业项目
很多同学下载“网上超市源码”后发现:首页能渲染,商品能列表,但一加购物车就报错,一提交订单就跳转空白页,后台日志里满屏NullPointerException;文档里写着“已集成微信支付”,点开代码却只有// TODO: 微信支付接入的注释;答辩时老师问“并发下单怎么防超卖”,回答“用了 synchronized”,结果被追问“synchronized 锁的是谁?分布式部署下还有效吗?”当场卡壳。本项目不是静态页面堆砌,它以真实电商最小可行逻辑为骨架——用户登录态贯穿前后端、购物车数据持久化到 MySQL、下单时基于数据库乐观锁实现库存原子扣减、订单状态机驱动发货/退款流程、Vue 路由守卫拦截未登录访问。它面向的是需要在 3 天内完成部署演示、7 天内通过三轮答辩、且代码能被导师逐行审查的毕业场景。技术选型明确拒绝“炫技陷阱”:SpringBoot 2.7.x(兼容 JDK 8,避开 SpringBoot 3.x 的 Jakarta EE 9 迁移雷区),Vue 2.6.14(稳定、生态成熟、调试工具链完善),不引入 Nacos、Seata 等复杂中间件,所有分布式一致性问题用数据库层面可验证的方案落地。你拿到的不是 ZIP 包里的幻灯片,而是能mvn clean package后直接java -jar启动、npm run serve就打开管理后台、所有接口在 Postman 里可手动触发验证的完整工程。
2. 搭建可运行环境:从零初始化 SpringBoot 后端与 Vue 前端,绕过常见 IDE 超时与依赖冲突
2.1 初始化 SpringBoot 2.7.18 工程(非 SpringBoot 3.x)并配置基础模块
使用 Spring Initializr(https://start.spring.io/)生成基础工程时,必须手动将 Spring Boot 版本锁定为2.7.18,而非默认最新版。这是因为 SpringBoot 3.x 强制要求 JDK 17+ 且包路径从javax.*迁移至jakarta.*,而多数高校实验室服务器仍为 JDK 8,且spring-boot-starter-web在 3.x 中移除了对@EnableWebMvc的隐式支持,会导致 Vue 前端跨域配置失效。生成时勾选以下 5 个核心依赖:
Spring WebMyBatis FrameworkMySQL DriverSpring Boot DevTools(开发期热重载)Lombok(消除 getter/setter 模板代码)
生成后,在pom.xml中确认<parent>标签内版本号为2.7.18,并显式添加 MyBatis 分页插件依赖(毕业项目高频需求):
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.6</version> </dependency>提示:若使用 IDEA 创建时遇到 “Connection timeout” 错误(头歌、部分校园网常见),请关闭 IDEA 内置 Maven,改用本地已安装的 Maven 3.8.6,并在
Settings > Build > Build Tools > Maven中指定Maven home path。超时本质是网络策略限制,非项目本身问题。
2.2 配置 application.yml 实现多环境分离与安全参数外置
src/main/resources/application.yml是整个后端行为的中枢。毕业项目需区分开发、测试、生产三套配置,避免答辩演示时误连正式数据库。关键配置如下(含注释说明):
# application.yml spring: profiles: active: dev # 默认启用 dev 环境 datasource: url: jdbc:mysql://localhost:3306/supermarket?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: 123456 # 开发环境明文可接受,但答辩前必须改为密文(见 4.2 节) driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0 timeout: 2000 mybatis: mapper-locations: classpath:mapper/*.xml # 指定 XML 映射文件位置 configuration: map-underscore-to-camel-case: true # 自动转换下划线字段名到驼峰属性名(如 user_name → userName) pagehelper: helper-dialect: mysql reasonable: true support-methods-arguments: true params: count=countSql # 自定义业务配置 supermarket: jwt: secret: supermarket2024key # JWT 密钥,答辩时需现场修改 expire: 86400 # 有效期 24 小时(秒) upload: path: D:/supermarket/upload/ # 文件上传根目录,Windows 路径需用正斜杠或双反斜杠注意:
upload.path必须是绝对路径且目录需提前手动创建,否则上传图片时会抛FileNotFoundException。Linux 环境下路径应为/home/user/supermarket/upload/。
2.3 初始化 Vue 2.6.14 前端工程并配置反向代理
使用 Vue CLI 4.5.19(非 Vue CLI 5,因 Vue 2 项目不兼容 CLI 5)创建前端:
npm install -g @vue/cli@4.5.19 vue create supermarket-vue # 选择 Manually select features → Babel, Router, Vuex, CSS Pre-processors (Sass), Linter # Use history mode for router? → Yes # Pick a CSS pre-processor → Sass # Pick a linter / formatter config → ESLint + Standard config # Where do you prefer placing config for Babel, ESLint, etc.? → In dedicated config files创建完成后,进入supermarket-vue目录,安装关键依赖:
npm install axios@0.21.4 element-ui@2.15.14 vue-router@3.5.3 vuex@3.6.2 --save npm install sass@1.58.3 sass-loader@10.4.1 --save-dev提示:
axios@0.21.4是 Vue 2 生态最稳定的版本,0.27+在 Promise 链处理上存在兼容性问题;element-ui@2.15.14是最后一个支持 IE11 的稳定版,满足高校机房老旧浏览器要求。
配置反向代理解决跨域(vue.config.js):
// vue.config.js module.exports = { devServer: { port: 8080, proxy: { '/api': { target: 'http://localhost:8081', // 后端 SpringBoot 默认端口 changeOrigin: true, pathRewrite: { '^/api': '' // 将请求路径中的 /api 替换为空,使后端接收 /user/login 而非 /api/user/login } } } } }此时执行npm run serve,前端启动在http://localhost:8080,所有/api/**请求自动转发至http://localhost:8081,无需在每个 axios 请求中写死后端地址。
3. 核心业务落地:从数据库建表到 Vue 组件联动,实现购物车与订单状态机
3.1 设计符合电商逻辑的 MySQL 表结构(含库存扣减关键字段)
毕业项目数据库设计常犯错误是忽略“库存快照”与“订单明细”的分离。本项目采用product(商品主表)、product_sku(SKU 库存表)、order_master(订单主表)、order_item(订单明细表)四表结构,确保下单时能精确扣减具体 SKU 库存。关键 SQL 如下(执行前请确认 MySQL 字符集为utf8mb4):
-- 商品主表(品牌、分类等信息) CREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT '商品名称', category_id BIGINT NOT NULL COMMENT '分类ID', status TINYINT DEFAULT 1 COMMENT '状态:0-下架,1-上架' ); -- SKU 库存表(同一商品不同规格,如颜色、尺寸) CREATE TABLE product_sku ( id BIGINT PRIMARY KEY AUTO_INCREMENT, product_id BIGINT NOT NULL COMMENT '关联商品ID', sku_code VARCHAR(50) UNIQUE NOT NULL COMMENT 'SKU编码', price DECIMAL(10,2) NOT NULL COMMENT '销售价格', stock INT NOT NULL DEFAULT 0 COMMENT '当前库存', version INT DEFAULT 0 COMMENT '乐观锁版本号,用于库存扣减', INDEX idx_product_id (product_id) ); -- 订单主表 CREATE TABLE order_master ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) UNIQUE NOT NULL COMMENT '订单号', user_id BIGINT NOT NULL COMMENT '用户ID', total_amount DECIMAL(10,2) NOT NULL COMMENT '订单总金额', status TINYINT DEFAULT 0 COMMENT '订单状态:0-待支付,1-已支付,2-已发货,3-已完成,4-已取消', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME NULL ); -- 订单明细表 CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL COMMENT '订单ID', sku_id BIGINT NOT NULL COMMENT 'SKU ID', quantity INT NOT NULL COMMENT '购买数量', price DECIMAL(10,2) NOT NULL COMMENT '下单时价格(防止价格变动)', INDEX idx_order_id (order_id) );注意:
product_sku.stock是实际库存字段,product_sku.version是乐观锁字段。下单时更新语句为UPDATE product_sku SET stock = stock - #{quantity}, version = version + 1 WHERE id = #{skuId} AND version = #{version},若WHERE条件不成立(即 version 不匹配),则扣减失败,需回滚事务并提示“库存不足”。
3.2 SpringBoot 层实现带乐观锁的库存扣减与订单创建
在OrderService中,createOrder()方法需在一个数据库事务中完成“校验库存 → 扣减库存 → 创建订单 → 创建明细”。关键代码如下(使用@Transactional保证原子性):
@Service public class OrderServiceImpl implements OrderService { @Autowired private ProductSkuMapper productSkuMapper; @Autowired private OrderMasterMapper orderMasterMapper; @Autowired private OrderItemMapper orderItemMapper; @Transactional(rollbackFor = Exception.class) @Override public String createOrder(Long userId, List<OrderItemDTO> items) { // 1. 校验并扣减每个 SKU 库存(乐观锁) for (OrderItemDTO item : items) { ProductSku sku = productSkuMapper.selectById(item.getSkuId()); if (sku == null || sku.getStock() < item.getQuantity()) { throw new RuntimeException("SKU " + item.getSkuId() + " 库存不足"); } // 执行带版本号的更新 int updated = productSkuMapper.decreaseStock( item.getSkuId(), item.getQuantity(), sku.getVersion() ); if (updated == 0) { throw new RuntimeException("库存扣减失败,请重试(可能已被其他用户抢购)"); } } // 2. 创建订单主表 OrderMaster master = new OrderMaster(); master.setOrderNo("ORD" + System.currentTimeMillis()); // 简单订单号生成 master.setUserId(userId); master.setTotalAmount(calculateTotal(items)); master.setStatus(0); // 待支付 orderMasterMapper.insert(master); // 3. 创建订单明细 for (OrderItemDTO item : items) { OrderItem orderItem = new OrderItem(); orderItem.setOrderId(master.getId()); orderItem.setSkuId(item.getSkuId()); orderItem.setQuantity(item.getQuantity()); orderItem.setPrice(item.getPrice()); orderItemMapper.insert(orderItem); } return master.getOrderNo(); } }对应的 MyBatis XML(ProductSkuMapper.xml)中decreaseStock方法:
<!-- ProductSkuMapper.xml --> <update id="decreaseStock"> UPDATE product_sku SET stock = stock - #{quantity}, version = version + 1 WHERE id = #{skuId} AND version = #{version} </update>提示:此方案在单体应用中完全可靠。若答辩被问及“高并发怎么办”,可补充:“在毕业项目规模下,MySQL 行锁已足够;若需更高性能,可引入 Redis 预减库存 + MQ 异步落库,但本项目聚焦核心逻辑验证,未过度设计。”
3.3 Vue 前端购物车组件与路由守卫实现状态同步
购物车数据需在页面刷新后依然存在,因此采用Vuex 持久化 + 本地存储方案。在store/modules/cart.js中:
// store/modules/cart.js const state = { items: JSON.parse(localStorage.getItem('cartItems')) || [] } const mutations = { ADD_TO_CART(state, item) { const exist = state.items.find(i => i.skuId === item.skuId) if (exist) { exist.quantity += item.quantity } else { state.items.push(item) } localStorage.setItem('cartItems', JSON.stringify(state.items)) }, REMOVE_FROM_CART(state, skuId) { state.items = state.items.filter(i => i.skuId !== skuId) localStorage.setItem('cartItems', JSON.stringify(state.items)) } } export default { namespaced: true, state, mutations }在商品详情页(ProductDetail.vue)中调用:
<template> <div> <el-button @click="addToCart">加入购物车</el-button> </div> </template> <script> import { mapMutations } from 'vuex' export default { methods: { ...mapMutations('cart', ['ADD_TO_CART']), addToCart() { const item = { skuId: this.product.skuId, name: this.product.name, price: this.product.price, quantity: this.cartQuantity } this.ADD_TO_CART(item) this.$message.success('已加入购物车') } } } </script>同时,使用 Vue Router 全局前置守卫拦截未登录访问(router/index.js):
// router/index.js router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next('/login') // 跳转登录页 } else { next() } })并在路由配置中为敏感页面(如/cart,/order/confirm)添加meta: { requiresAuth: true }。
4. 高分答辩关键点:JWT 登录鉴权、YML 密文配置、订单状态流转验证
4.1 基于 JWT 的前后端分离登录与权限控制
毕业项目常被质疑“登录怎么保证安全”。本项目采用标准 JWT 流程:前端传用户名密码 → 后端校验 → 生成 Token(含用户 ID、角色、过期时间)→ 前端存储 Token 并在后续请求 Header 中携带 → 后端拦截器解析验证。关键步骤:
- 后端生成 Token(LoginController):
@PostMapping("/login") public Result login(@RequestBody LoginDTO dto) { User user = userService.findByUsername(dto.getUsername()); if (user != null && BCrypt.checkpw(dto.getPassword(), user.getPassword())) { String token = JwtUtil.generateToken(user.getId(), user.getRole()); return Result.success(token); } return Result.fail("用户名或密码错误"); }- 前端 Axios 请求拦截器自动携带 Token:
// utils/request.js service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = 'Bearer ' + token } return config })- 后端 JWT 拦截器(JwtAuthenticationFilter):
public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = resolveToken(request); if (token != null && JwtUtil.validateToken(token)) { Long userId = JwtUtil.getUserId(token); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken( userId, null, Collections.emptyList() ); SecurityContextHolder.getContext().setAuthentication(auth); } filterChain.doFilter(request, response); } }提示:答辩时可现场演示用 Postman 发送
GET /api/user/info请求,Header 中添加Authorization: Bearer xxx,返回正确用户信息;再删除 Header,返回401 Unauthorized,证明鉴权生效。
4.2 将 application.yml 中的数据库密码加密为密文
明文密码在答辩中属于严重安全隐患。SpringBoot 官方推荐使用 Jasypt 加密。在pom.xml中添加:
<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.4</version> </dependency>启动类添加注解:
@SpringBootApplication @EnableEncryptableProperties public class SupermarketApplication { public static void main(String[] args) { SpringApplication.run(SupermarketApplication.class, args); } }使用 Jasypt 命令行工具加密密码(需先下载 jasypt-1.9.3.jar):
java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="123456" password=supermarket2024key algorithm=PBEWithMD5AndDES # 输出:qQZJzXkKvFwVxGtHrYmNpLcQaSdFgHjK将application.yml中密码替换为:
spring: datasource: password: ENC(qQZJzXkKvFwVxGtHrYmNpLcQaSdFgHjK) # ENC() 包裹密文启动时添加 JVM 参数指定加密密钥:
java -Djasypt.encryptor.password=supermarket2024key -jar supermarket.jar注意:
jasypt.encryptor.password必须与加密时使用的password一致,且该密钥不能硬编码在代码中,答辩时需说明“密钥由运维人员线下提供,不纳入代码仓库”。
4.3 通过数据库查询验证订单状态机是否按预期流转
高分项目需提供可验证的状态流转证据。本项目订单状态机为:0(待支付)→ 1(已支付)→ 2(已发货)→ 3(已完成),禁止跳变(如 0 直接到 2)。验证方法如下:
- 手动触发支付成功回调(模拟微信支付通知):
curl -X POST http://localhost:8081/api/order/paySuccess \ -H "Content-Type: application/json" \ -d '{"orderNo":"ORD17123456789","payAmount":99.99}'- 查询数据库确认状态变更:
-- 查看订单主表状态 SELECT order_no, status, pay_time FROM order_master WHERE order_no = 'ORD17123456789'; -- 查看订单明细是否已生成 SELECT * FROM order_item WHERE order_id = (SELECT id FROM order_master WHERE order_no = 'ORD17123456789');- 检查日志输出(
logs/supermarket.log):
2024-04-05 14:22:33.123 INFO 12345 --- [nio-8081-exec-7] c.s.s.o.OrderService : 订单 ORD17123456789 支付成功,状态更新为 1 2024-04-05 14:22:33.125 INFO 12345 --- [nio-8081-exec-7] c.s.s.o.OrderService : 已向消息队列发送发货任务(模拟)提示:答辩时可打开数据库客户端(如 Navicat)和日志文件,实时演示从下单、支付、发货到完成的全链路数据变化,比口头描述更具说服力。
本文还有配套的精品资源,点击获取