ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpringBoot+Vue网上超市毕业项目:支付闭环与库存扣减实战

SpringBoot+Vue网上超市毕业项目:支付闭环与库存扣减实战 简介这是一套基于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 Initializrhttps://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 groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.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?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue 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: countcountSql # 自定义业务配置 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/cli4.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 axios0.21.4 element-ui2.15.14 vue-router3.5.3 vuex3.6.2 --save npm install sass1.58.3 sass-loader10.4.1 --save-dev提示axios0.21.4是 Vue 2 生态最稳定的版本0.27在 Promise 链处理上存在兼容性问题element-ui2.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_skuSKU 库存表、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, ListOrderItemDTO 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 XMLProductSkuMapper.xml中decreaseStock方法!-- ProductSkuMapper.xml -- update iddecreaseStock 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 clickaddToCart加入购物车/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 中携带 → 后端拦截器解析验证。关键步骤后端生成 TokenLoginControllerPostMapping(/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 拦截器JwtAuthenticationFilterpublic 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 groupIdcom.github.ulisesbocchio/groupId artifactIdjasypt-spring-boot-starter/artifactId version3.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.jarjava -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input123456 passwordsupermarket2024key algorithmPBEWithMD5AndDES # 输出qQZJzXkKvFwVxGtHrYmNpLcQaSdFgHjK将application.yml中密码替换为spring: datasource: password: ENC(qQZJzXkKvFwVxGtHrYmNpLcQaSdFgHjK) # ENC() 包裹密文启动时添加 JVM 参数指定加密密钥java -Djasypt.encryptor.passwordsupermarket2024key -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.log2024-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和日志文件实时演示从下单、支付、发货到完成的全链路数据变化比口头描述更具说服力。本文还有配套的精品资源点击获取
返回列表