ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue+MySQL网上购物商城系统实战:从零跑通最小下单链路

SpringBoot+Vue+MySQL网上购物商城系统实战:从零跑通最小下单链路 简介这是一套面向Java Web初学者与课程设计者的网上购物商城系统完整资料基于Spring Boot框架、Vue前端与MySQL数据库开发适合作为毕业设计、课程大作业或全栈入门练手项目。压缩包共774个文件约19.59MB其中121个Java文件承载后端业务逻辑46个Vue组件与153个JS文件构成前端交互层另有44个CSS、40个HTML及大量svg、png、jpg等静态资源并附带sql建库脚本、yml配置、bat启动脚本与论文文档结构清晰、便于二次开发。资源完整覆盖需求分析、功能模块设计、系统测试与总结等环节读者可据此掌握商城系统的整体架构与实现思路快速搭建可运行环境并参考论文框架完成自己的设计文档。目前已有84人学习下载适合需要一套可直接参考的Spring BootVue实战案例的开发者。1. 从零搭一套网上购物商城为什么我劝你先跑通这条最小链路很多人做网上购物商城系统第一反应是打开 IDE 新建 SpringBoot 工程然后对着 Vue 的脚手架发呆最后卡在 MySQL 连不上。我带过几届做课程设计的学生十个里有八个在“环境搭好了但跑不起来”这一步耗掉一周。问题不在技术难而在于网上购物商城系统这条链路太长SpringBoot 要连 MySQLVue 要调后端接口中间还夹着跨域、端口、依赖版本。任何一个环节对不上整个系统就是黑匣子报错信息还互相甩锅。这篇笔记只干一件事把基于 SpringBoot Vue MySQL 的网上购物商城系统从环境到能下单的最小闭环讲透。适合两类人——刚接触 SpringBoot 项目结构、想拿一个完整业务练手的入门者以及做过单体项目、但没把前后端分离链路真正打通的人。我不会贴一份不存在的源码目录而是按我实际搭这套东西的顺序把每一步的命令、参数和翻车点写清楚。你照着走能拿到一个能登录、能浏览商品、能加购物车、能下单的骨架剩下的业务照着这个骨架长就行。2. 技术选型与工程骨架SpringBoot 和 Vue 到底怎么分工2.1 为什么是 SpringBoot 做后端、Vue 做前端网上购物商城系统的核心是“数据 交互”。商品、订单、用户这些数据要持久化库存扣减要保证一致性这些是后端的事页面渲染、路由跳转、表单校验这些是前端的事。SpringBoot 的价值在于它把 Spring 那套 XML 配置压成了注解和 starter一个spring-boot-starter-web就能起 HTTP 服务spring-boot-starter-data-jpa或 MyBatis 就能连 MySQL。对商城这种 CRUD 密集、又要快速迭代的项目SpringBoot 的自动配置省掉大量胶水代码。Vue 这边商城前端天然是组件化的商品卡片、购物车条目、订单列表每个都是独立组件。Vue 的响应式数据绑定让“加购物车后角标数字变化”这种交互不用手动操作 DOM。前后端分离后Vue 通过 axios 调 SpringBoot 暴露的 REST 接口两边可以并行开发前端用 mock 数据先跑后端接口好了再联调。提示如果你的 SpringBoot 版本太高比如 3.x默认要求 JDK 17而很多教程和依赖还停留在 JDK 8。新手建议先用 SpringBoot 2.7.x JDK 8 或 11等链路跑通再升级。2.2 工程骨架怎么分目录我一般把项目分成两个独立仓库或两个顶层目录mall-backend和mall-frontend。后端用 Maven 构建目录结构按 SpringBoot 约定来mall-backend ├── src/main/java/com/example/mall │ ├── controller // 接收前端请求 │ ├── service // 业务逻辑比如扣库存 │ ├── mapper // MyBatis 接口对应 SQL │ ├── entity // 数据库表映射 │ └── config // 跨域、拦截器等配置 ├── src/main/resources │ ├── application.yml // 数据库、端口配置 │ └── mapper // MyBatis XML └── pom.xml前端用 Vue CLI 或 Vite 创建目录里src/api放接口封装src/views放页面src/router放路由。这样分的好处是后端改接口不影响前端目录前端换 UI 框架也不动后端。2.3 用 Maven 起后端工程的最小命令不依赖 IDE 的图形界面直接用命令行生成骨架能让你看清每一步到底生成了什么。先确认 Maven 和 JDK 装好java -version mvn -version然后创建一个 SpringBoot 工程。常见做法是用 Spring Initializr 的 Web 界面下载但命令行也能做curl https://start.spring.io/starter.zip \ -d typemaven-project \ -d languagejava \ -d bootVersion2.7.18 \ -d groupIdcom.example \ -d artifactIdmall-backend \ -d namemall-backend \ -d packageNamecom.example.mall \ -d dependenciesweb,mysql,mybatis \ -o mall-backend.zip unzip mall-backend.zip -d mall-backend这段命令向 Spring Initializr 请求一个压缩包参数含义bootVersion指定 SpringBoot 版本dependencies里web是 Web 起步依赖mysql是 MySQL 驱动mybatis是 MyBatis 集成。下载后解压pom.xml里已经带好这些依赖。如果你网络访问不了这个地址就在 IDE 里用 Spring Initializr 插件生成选同样的依赖。生成后进目录跑一下cd mall-backend mvn spring-boot:run看到Started MallBackendApplication就说明后端骨架起来了。默认端口 8080此时访问http://localhost:8080会返回 404因为还没写任何 Controller这是正常的。2.4 Vue 前端工程初始化与依赖安装前端我倾向用 Vite 创建 Vue 3 项目比 Vue CLI 快很多npm create vitelatest mall-frontend -- --template vue cd mall-frontend npm install npm install axios vue-routernpm create vite用 Vite 的模板生成 Vue 3 工程npm install装基础依赖再单独装axios发 HTTP 请求和vue-router页面路由。装完跑npm run dev默认在 5173 端口起一个开发服务器。这里有个高频翻车点前端 5173 调后端 8080浏览器会因跨域拦截请求。解决办法是在后端加 CORS 配置而不是在前端瞎改。在 SpringBoot 里加一个配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) // 开发阶段放开上线要收紧 .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true); } }addMapping(/**)表示对所有接口生效allowedOriginPatterns(*)允许任意来源allowCredentials(true)允许携带 Cookie。注意上线时要把*换成具体域名否则等于没做安全限制。3. MySQL 建库建表与 SpringBoot 数据源配置3.1 商城核心表怎么设计网上购物商城系统最少要五张表用户、商品、购物车、订单、订单明细。表结构不用一上来就追求完美先把主键、外键和几个关键字段定下来。下面是我常用的最小表结构用 SQL 直接建CREATE DATABASE mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE mall; CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, nickname VARCHAR(50), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, cover VARCHAR(255), description TEXT ); CREATE TABLE cart_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, product_id BIGINT NOT NULL, quantity INT NOT NULL DEFAULT 1, UNIQUE KEY uk_user_product (user_id, product_id) ); CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, -- 0待支付 1已支付 2已发货 3已完成 created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, product_id BIGINT NOT NULL, quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL );几个设计点值得说cart_item上加了uk_user_product唯一索引保证同一用户同一商品只有一条购物车记录加购时用INSERT ... ON DUPLICATE KEY UPDATE quantity quantity 1就能避免重复行。orders的status用 TINYINT 而不是字符串查询和索引都更省。金额一律用DECIMAL(10,2)绝不用 FLOAT否则对账时会出现 0.1 0.2 不等于 0.3 的玄学。3.2 application.yml 里数据源怎么配MySQL 建好库后回到 SpringBoot 的application.ymlserver: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/mall?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.mall.entityurl里的参数逐个说useUnicodetruecharacterEncodingutf8保证中文不乱码serverTimezoneAsia/Shanghai解决时区差 8 小时的问题useSSLfalse在本地开发时关掉 SSL否则 MySQL 8 会报 SSL 连接错误。driver-class-name用com.mysql.cj.jdbc.Driver这是 MySQL 8 驱动的类名用旧版com.mysql.jdbc.Driver会警告。注意如果你在 Windows 10 上装 MySQL 8.0安装时会让选认证方式选Use Legacy Authentication Method兼容性更好否则 SpringBoot 连接可能报Public Key Retrieval is not allowed这时在 url 后面加allowPublicKeyRetrievaltrue也能解决。3.3 用 MyBatis 写第一个查询接口数据源通了之后写一个商品列表接口验证链路。先建实体类public class Product { private Long id; private String name; private BigDecimal price; private Integer stock; private String cover; // getter/setter 省略 }再建 Mapper 接口和 XMLMapper public interface ProductMapper { ListProduct selectAll(); }mapper namespacecom.example.mall.mapper.ProductMapper select idselectAll resultTypecom.example.mall.entity.Product SELECT id, name, price, stock, cover FROM product ORDER BY id DESC /select /mapperresultType写实体类全限定名MyBatis 会自动把列名映射到属性名下划线转驼峰需要开启map-underscore-to-camel-case在 yml 里加mybatis.configuration.map-underscore-to-camel-case: true。Controller 里注入 Mapper 返回 JSONRestController RequestMapping(/api/product) public class ProductController { Autowired private ProductMapper productMapper; GetMapping(/list) public ListProduct list() { return productMapper.selectAll(); } }启动后访问http://localhost:8080/api/product/list如果返回[]说明链路通了只是表里没数据。插两条测试数据再刷新就能看到 JSON。4. 前后端联调从商品列表到下单的完整链路4.1 Vue 里用 axios 封装接口请求前端不直接在每个组件里写 axios而是统一封装。在src/api/request.js里import axios from axios const request axios.create({ baseURL: http://localhost:8080/api, timeout: 5000 }) request.interceptors.response.use( response response.data, error { console.error(请求失败, error) return Promise.reject(error) } ) export default requestbaseURL指向后端地址这样组件里只写/product/list就行。响应拦截器直接把response.data返回省去每次.data。超时设 5 秒开发阶段够用。然后在src/api/product.js里定义具体接口import request from ./request export function fetchProducts() { return request.get(/product/list) }组件里onMounted时调用fetchProducts()把返回数组赋给ref模板里v-for渲染商品卡片。这一步跑通说明 Vue 能拿到 SpringBoot 的数据前后端链路正式打通。4.2 加购物车与库存扣减的事务处理加购物车接口要处理“已存在则数量加一”的逻辑。Service 层Service public class CartService { Autowired private CartItemMapper cartItemMapper; public void addToCart(Long userId, Long productId, int quantity) { cartItemMapper.insertOrUpdate(userId, productId, quantity); } }对应 XMLinsert idinsertOrUpdate INSERT INTO cart_item (user_id, product_id, quantity) VALUES (#{userId}, #{productId}, #{quantity}) ON DUPLICATE KEY UPDATE quantity quantity #{quantity} /insertON DUPLICATE KEY UPDATE依赖前面建的唯一索引一条 SQL 搞定插入或累加避免先查后插的并发问题。下单是更关键的一步涉及扣库存和写订单必须放在一个事务里Service public class OrderService { Autowired private ProductMapper productMapper; Autowired private OrderMapper orderMapper; Autowired private OrderItemMapper orderItemMapper; Transactional public Long createOrder(Long userId, ListOrderItem items) { BigDecimal total BigDecimal.ZERO; for (OrderItem item : items) { int affected productMapper.reduceStock(item.getProductId(), item.getQuantity()); if (affected 0) { throw new RuntimeException(库存不足: item.getProductId()); } total total.add(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))); } Order order new Order(); order.setUserId(userId); order.setTotalAmount(total); order.setStatus(0); orderMapper.insert(order); for (OrderItem item : items) { item.setOrderId(order.getId()); orderItemMapper.insert(item); } return order.getId(); } }Transactional保证扣库存和写订单要么全成功要么全回滚。reduceStock的 SQL 要带条件update idreduceStock UPDATE product SET stock stock - #{quantity} WHERE id #{productId} AND stock #{quantity} /updateAND stock #{quantity}是关键它让库存不足时更新影响行数为 0Service 里据此抛异常回滚。如果只写stock stock - quantity库存会被扣成负数这是商城系统最典型的翻车点。4.3 用 Vue Router 组织页面跳转商城至少三个页面商品列表、购物车、订单确认。在src/router/index.js里配置import { createRouter, createWebHistory } from vue-router import ProductList from ../views/ProductList.vue import Cart from ../views/Cart.vue import OrderConfirm from ../views/OrderConfirm.vue const routes [ { path: /, component: ProductList }, { path: /cart, component: Cart }, { path: /order, component: OrderConfirm } ] export default createRouter({ history: createWebHistory(), routes })createWebHistory用 HTML5 History 模式URL 干净没有#。但要注意这种模式下刷新页面如果后端没配 fallback会 404。开发阶段 Vite 自带处理上线时要么用 hash 模式要么在 Nginx 里配try_files。路由参数传递用router.push({ path: /order, query: { ids: 1,2 } })目标页用route.query.ids取。购物车到订单确认页传商品 id 列表订单页再调后端接口拿详情。5. 避坑与排查这套商城系统最容易翻车的五个地方5.1 跨域报错但后端明明配了 CORS现象浏览器控制台报Access to XMLHttpRequest has been blocked by CORS policy但后端 CorsConfig 已经写了。原因多半是 CorsConfig 没生效或者被 Spring Security 拦截器覆盖。如果项目引入了 Spring Security它的过滤器链在 CORS 之前需要在 Security 配置里显式开启http.cors()。另一个常见原因是allowedOrigins(*)和allowCredentials(true)同时用浏览器会拒绝必须改成allowedOriginPatterns(*)。解决先确认 CorsConfig 类在com.example.mall.config包下且被Configuration标注有 Security 的话加http.cors(Customizer.withDefaults())把allowedOrigins换成allowedOriginPatterns。5.2 MySQL 8 连接报时区或 SSL 错误现象启动时报The server time zone value ?D1ú±ê×?ê±?? is unrecognized或SSL connection error。原因MySQL 8 默认时区是 UTC 且要求 SSL而 JDBC url 没带对应参数。解决url 里加serverTimezoneAsia/ShanghaiuseSSLfalse。如果还报Public Key Retrieval is not allowed再加allowPublicKeyRetrievaltrue。这三个参数基本能覆盖本地开发的连接问题。5.3 前端打包后放进 SpringBoot 静态目录刷新 404现象npm run build生成的 dist 放进src/main/resources/static访问首页正常但刷新/cart就 404。原因Vue Router 的 history 模式依赖服务端把所有路径 fallback 到 index.htmlSpringBoot 默认只认静态文件路径。解决加一个 Controller 或配置类把非 API 路径转发到 index.htmlController public class SpaController { RequestMapping(value /{path:[^\\.]*}) public String forward() { return forward:/index.html; } }正则[^\\.]*表示不含点的路径才转发避免把.js、.css也转发了。5.4 下单接口并发时库存扣成负数现象压测或多人同时下单商品库存变成负数。原因扣库存 SQL 没加stock quantity条件或者加了但没在事务里判断影响行数。解决SQL 必须带AND stock #{quantity}Service 里判断affected 0就抛异常。更严格的做法是用乐观锁版本号但对课程设计级别的商城条件更新已经够用。5.5 Vue 依赖装不上或版本冲突现象npm install卡住或报ERESOLVE unable to resolve dependency tree。原因npm 7 以后对 peerDependencies 检查更严Vue 3 生态里某些库还声明依赖 Vue 2。解决先试npm install --legacy-peer-deps它能跳过 peer 依赖检查。如果还不行删掉node_modules和package-lock.json重装。国内网络慢的话配一下 npm 镜像源但别用来源不明的镜像。6. 把商城跑稳之后我习惯先做这三件事链路跑通只是起点。我每次搭完一个商城骨架会先做三件事来验证它是不是真的“能用”而不是“能跑”。第一件是写一个下单的并发测试。用 JMeter 或简单的ab命令对下单接口发 50 个并发请求看库存是不是刚好扣到 0 而不是负数。命令很简单ab -n 50 -c 10 -p order.json -T application/json http://localhost:8080/api/order/create-n 50总请求数-c 10并发数-p指定 POST 的 JSON 文件。跑完看数据库product.stock如果出现负数说明事务或条件更新还有漏洞。这个测试比任何单元测试都直观。第二件是把所有金额字段的 Java 类型检查一遍。我见过太多项目用double存价格结果订单总额出现199.99999999。统一用BigDecimal并且乘法用multiply、加法用add不要用和*。前端展示时用toFixed(2)格式化但存储和计算绝不用浮点。第三件是给所有列表接口加分页。商品表数据一多SELECT *全查会把接口拖垮。MyBatis 分页用LIMIT #{offset}, #{size}或者引入 PageHelper。分页参数从请求里取默认page1, size10并且对size设上限比如最大 100防止有人传size999999把内存打爆。最后说个我自己的习惯每加一个新接口先用 Postman 或 curl 单独测通再接到前端。前后端一起调的时候报错信息会互相干扰先隔离验证能省一半时间。这套商城系统不难难的是把每个环节的边界条件想到。希望帮到你。本文还有配套的精品资源点击获取
返回列表