SpringBoot+Vue3+MySQL商城源码:从环境搭建到订单落库全链路实战
简介这是一套面向高校计算机专业毕业设计的商城购物网站完整项目源码基于SpringBoot与Vue3.0技术栈配合MySQL数据库实现适合正在准备毕设或需要全栈实战练习的开发者参考。项目分为前台与后台两大模块前台涵盖用户注册登录、商品查询与购买、订单管理、个人中心地址与资料管理后台包含用户管理、商品类型与商品管理、订单发货处理、评论管理及系统设置功能链路完整可直接作为毕设选题的落地实现。资源包共314个文件以120个Java后端源码、30个Vue组件、90个CSS样式、25个JS脚本及14个XML配置为主另含数据库脚本、图片与字体等静态资源压缩包约910KB结构清晰便于按模块阅读。目前已有184人学习下载读者可据此快速理解前后端分离架构、接口设计与数据库表结构并在此基础上完成二次开发与论文撰写。1. 从一份能跑通的商城源码说起SpringBootVue3MySQL 到底交付了什么很多同学做毕业设计卡在同一个地方需求文档写完了ER 图画完了真到敲代码时发现前后端联调、数据库建表、分页查询、订单状态流转这些环节一个都绕不过去。这份基于 SpringBoot Vue3.0 MySQL 的商城购物网站源码解决的正是「从设计到能跑起来」这一段。它包含完整的 Java 后端源码、Vue3 前端工程和数据库脚本覆盖商品展示、购物车、下单、用户管理等商城核心链路。适合正在做 JavaWeb 方向毕业设计、课程设计或者想拿一个前后端分离项目练手 SpringBoot 和 Vue3 的开发者。你拿到手之后重点不是从头读代码而是先把它跑起来再顺着一条业务线往下拆。2. 环境准备与数据库落地MySQL 建库、SpringBoot 配置、Vue3 启动顺序2.1 三个组件的版本匹配与安装顺序这套项目是典型的前后端分离结构后端 SpringBoot 提供 REST 接口前端 Vue3 通过 axios 调用MySQL 存数据。跑起来之前三个东西的版本要对上不然会出现「后端能启动但前端 404」或者「数据库连不上」这类玄学问题。常见做法是先装 JDK建议 JDK 8 或 JDK 11SpringBoot 2.x 对这两个版本兼容最好。然后是 MySQL5.7 和 8.0 都能用但要注意驱动类名和连接 URL 的差异。最后是 Node.jsVue3 项目一般需要 Node 14 以上推荐 16 或 18 的 LTS 版本。安装顺序建议JDK → MySQL → Node.js → 后端启动 → 前端启动。这个顺序不是随便定的因为后端启动时会去连 MySQL前端启动时会去调后端接口前面的没起来后面的必然报错。提示如果你之前装过其他版本的 JDK 或 MySQL先确认环境变量指向的是当前要用的版本java -version和mysql --version各跑一遍别嫌麻烦。2.2 数据库脚本导入与连接配置数据库脚本一般是一个.sql文件里面包含建库、建表、插入初始数据三部分。导入方式有两种命令行和图形化工具。命令行更稳图形化工具更直观看你习惯。用命令行导入的步骤# 登录 MySQL输入密码后进入 mysql -u root -p # 创建数据库如果脚本里没有 CREATE DATABASE 语句 CREATE DATABASE mall_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 切换到该数据库 USE mall_db; # 导入脚本注意路径换成你本地的实际路径 source /path/to/your/mall.sql; # 验证表是否建好 SHOW TABLES;如果你用 Navicat 或者 MySQL Workbench操作更简单新建连接 → 新建数据库 → 右键「运行 SQL 文件」→ 选择脚本 → 执行。执行完刷新一下看到表列表就说明成功了。导入之后要检查两件事一是字符集确保是utf8mb4不然中文商品名会变问号二是初始数据有些脚本会插入管理员账号和测试商品确认这些数据在不在后面登录要用。接下来改后端配置文件。SpringBoot 的数据库配置通常在application.yml或application.properties里找到spring.datasource这一段spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mall_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 你的密码几个参数说明一下。driver-class-name这一行MySQL 8.0 用com.mysql.cj.jdbc.Driver5.7 用com.mysql.jdbc.Driver写错了启动直接报错。url里的serverTimezone必须加不然 MySQL 8.0 会报时区异常。useSSLfalse是关掉 SSL 连接本地开发没必要开开了反而容易出连接错误。characterEncodingutf8保证中文不乱码。注意密码不要加引号除非你的密码里真的有特殊字符。很多人在这里多写了引号导致连接失败排查半天。2.3 后端启动与前端联调数据库通了之后启动后端。如果你用 IDEA找到主启动类右键 Run 就行。如果用命令行# 在项目根目录下先打包再运行 mvn clean package -DskipTests # 运行 jar 包 java -jar target/mall-0.0.1-SNAPSHOT.jar启动成功的标志是控制台出现Started Application in x.x seconds并且没有异常堆栈。如果报Communications link failure说明数据库没连上回去检查 URL、用户名、密码和 MySQL 服务是否在运行。后端起来之后先别急着启动前端用浏览器或者 Postman 访问一下后端接口确认能返回数据。比如商品列表接口一般是/api/goods/list或者/goods/list具体路径看 Controller 里的RequestMapping。返回 JSON 数据就说明后端没问题。前端启动# 进入前端目录 cd frontend # 安装依赖国内建议换淘宝镜像 npm install --registryhttps://registry.npmmirror.com # 启动开发服务器 npm run serve启动后控制台会输出本地访问地址一般是http://localhost:8080或http://localhost:8081。打开浏览器访问如果页面正常渲染但数据加载不出来按 F12 看 Network 面板大概率是接口地址配错了。前端调用后端的地址通常在src/utils/request.js或者.env.development文件里确认它指向的是后端实际运行的端口。3. 商城核心业务链路拆解从商品列表到订单落库的代码走读3.1 商品模块分页查询与条件筛选的实现商品列表是商城的入口也是最能体现前后端分离套路的模块。前端发请求带分页参数和筛选条件后端用 MyBatis 或 MyBatis-Plus 查数据库返回分页结果。先看后端的分页查询。如果用 MyBatis-Plus分页需要先配置拦截器Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 添加分页拦截器指定数据库类型为 MySQL interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }这段代码的作用是告诉 MyBatis-Plus 怎么拼分页 SQL。没有这个配置Page对象传进去也不会生效查出来永远是全量数据。DbType.MYSQL指定了数据库方言不同数据库分页语法不一样写错了会报 SQL 语法错误。Service 层的分页查询public PageGoods getGoodsPage(int pageNum, int pageSize, String keyword, Long categoryId) { // 构建分页对象pageNum 是当前页码pageSize 是每页条数 PageGoods page new Page(pageNum, pageSize); // 构建查询条件 LambdaQueryWrapperGoods wrapper new LambdaQueryWrapper(); // 如果有关键词按商品名模糊匹配 if (StringUtils.hasText(keyword)) { wrapper.like(Goods::getName, keyword); } // 如果有分类筛选按分类 ID 精确匹配 if (categoryId ! null) { wrapper.eq(Goods::getCategoryId, categoryId); } // 按创建时间倒序排列 wrapper.orderByDesc(Goods::getCreateTime); return goodsMapper.selectPage(page, wrapper); }Page对象封装了页码和每页条数LambdaQueryWrapper用来动态拼条件。like是模糊查询对应 SQL 的LIKE %keyword%eq是等值查询对应。orderByDesc按创建时间倒序保证新商品排在前面。这些条件都是可选的前端传了就加没传就跳过这样一套代码能覆盖多种筛选场景。前端这边Vue3 用ref或reactive管理分页状态用 axios 发请求import { ref, onMounted } from vue import axios from axios const goodsList ref([]) const pageNum ref(1) const pageSize ref(10) const total ref(0) const keyword ref() const fetchGoods async () { try { const res await axios.get(/api/goods/page, { params: { pageNum: pageNum.value, pageSize: pageSize.value, keyword: keyword.value } }) // 后端返回结构一般是 { code, data: { records, total } } goodsList.value res.data.data.records total.value res.data.data.total } catch (error) { console.error(商品列表加载失败, error) } } onMounted(() { fetchGoods() })params里的字段名要和后端 Controller 接收的参数名一致不一致就传不过去。res.data.data.records这个路径取决于后端返回的 JSON 结构有的是res.data.data.list有的是res.data.rows对着后端实际返回改。onMounted保证组件加载完就拉一次数据。3.2 购物车与订单状态流转和库存扣减购物车模块的核心是把商品加进去、改数量、删掉然后结算生成订单。订单模块的核心是状态流转待付款 → 已付款 → 已发货 → 已完成中间还可能插入取消和退款。购物车加商品的逻辑public void addToCart(Long userId, Long goodsId, Integer num) { // 先查购物车里有没有这条商品 Cart existing cartMapper.selectOne( new LambdaQueryWrapperCart() .eq(Cart::getUserId, userId) .eq(Cart::getGoodsId, goodsId) ); if (existing ! null) { // 已有则累加数量 existing.setNum(existing.getNum() num); cartMapper.updateById(existing); } else { // 没有则新增一条 Cart cart new Cart(); cart.setUserId(userId); cart.setGoodsId(goodsId); cart.setNum(num); cart.setCreateTime(new Date()); cartMapper.insert(cart); } }这段逻辑看着简单但有个坑并发情况下同一个用户同时点两次「加入购物车」可能查出两条都不存在然后插入两条重复记录。常见做法是给user_id goods_id加唯一索引插入冲突时改成更新。这个在毕业设计里不一定要求但面试被问到就是加分项。下单时扣库存是另一个关键点Transactional(rollbackFor Exception.class) public void createOrder(Long userId, ListCartItem items) { for (CartItem item : items) { // 扣库存用乐观锁或条件更新防止超卖 int affected goodsMapper.reduceStock(item.getGoodsId(), item.getNum()); if (affected 0) { throw new RuntimeException(库存不足 item.getGoodsName()); } } // 创建订单主表和明细表 Order order new Order(); order.setUserId(userId); order.setOrderNo(generateOrderNo()); order.setStatus(0); // 0 表示待付款 orderMapper.insert(order); // ... 插入订单明细 }Transactional保证扣库存和创建订单在同一个事务里任何一步失败都回滚。reduceStock对应的 SQL 应该是UPDATE goods SET stock stock - #{num} WHERE id #{id} AND stock #{num}AND stock #{num}这个条件很关键它保证库存不够时更新影响行数为 0从而触发异常回滚。如果写成先查库存再更新并发下就会超卖。订单状态流转一般用一个status字段表示配合枚举类管理public enum OrderStatus { UNPAID(0, 待付款), PAID(1, 已付款), SHIPPED(2, 已发货), COMPLETED(3, 已完成), CANCELLED(4, 已取消); private final int code; private final String desc; // 构造方法和 getter 省略 }状态变更时要做校验比如只有待付款的订单能取消只有已付款的能发货。这个校验放在 Service 层别放 Controller不然业务逻辑散得到处都是。3.3 用户认证登录态与接口鉴权商城项目一般用 JWT 做登录态。用户登录成功后后端生成一个 token 返回给前端前端存到 localStorage 或 sessionStorage之后每次请求在 header 里带上这个 token。后端生成和校验 token// 生成 token public String generateToken(Long userId, String username) { MapString, Object claims new HashMap(); claims.put(userId, userId); claims.put(username, username); return Jwts.builder() .setClaims(claims) .setSubject(username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 86400000)) // 24小时过期 .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } // 校验 token public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); }SECRET_KEY要足够复杂别用123456这种不然 token 能被伪造。setExpiration设置过期时间毕业设计里设 24 小时够用了。前端在 axios 拦截器里统一加 tokenaxios.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config })后端用一个拦截器或过滤器统一校验放行登录和注册接口其他接口都要验 token。这样就不用每个 Controller 方法里都写一遍校验逻辑。4. 避坑与排查跑不起来、数据不对、页面空白怎么查4.1 后端启动报数据库连接失败现象启动时控制台报Communications link failure或Access denied for user。原因三种可能——MySQL 服务没启动、连接 URL 或端口写错、用户名密码不对。解决先确认 MySQL 在运行systemctl status mysql或 Windows 服务列表里看。然后检查application.yml里的 URL端口默认 3306如果你改过要对应改。最后确认用户名密码可以在命令行用mysql -u root -p手动登录验证。如果密码忘了MySQL 8.0 可以用ALTER USER rootlocalhost IDENTIFIED BY 新密码;重置。4.2 前端页面空白或接口 404现象浏览器打开前端地址页面一片空白或者控制台报 404。原因前端依赖没装全、后端没启动、接口地址配错、跨域被拦。解决先看浏览器控制台有没有 JS 报错有的话大概率是依赖问题删掉node_modules重新npm install。如果 Network 里接口请求返回 404检查前端配置的接口前缀和后端实际路径是否一致。如果报 CORS 跨域错误后端加一个全局跨域配置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(*)在 SpringBoot 2.4 以上版本用低版本用allowedOrigins(*)。allowCredentials(true)允许携带 cookie如果前端用 token 放 header 里这个可以不设。4.3 中文乱码现象商品名、用户名显示成问号或乱码。原因数据库字符集不是utf8mb4或者连接 URL 没加characterEncodingutf8。解决先查数据库字符集SHOW VARIABLES LIKE character%;确认character_set_database是utf8mb4。不是的话改数据库默认字符集或者建库时指定。然后确认连接 URL 里有characterEncodingutf8。如果还不行检查前端页面meta charsetutf-8有没有。4.4 分页查询返回全量数据现象传了pageNum和pageSize但返回的还是所有数据。原因MyBatis-Plus 分页拦截器没配或者配了但没生效。解决确认MybatisPlusConfig类被 Spring 扫描到加了Configuration注解。确认拦截器里DbType.MYSQL写对了。如果用的是 PageHelper确认PageHelper.startPage(pageNum, pageSize)在查询方法之前调用且只对紧接着的那一条查询生效。4.5 订单重复提交现象用户快速点两次「提交订单」生成了两笔一样的订单。原因前端没做防重复点击后端没做幂等校验。解决前端在提交按钮上加loading状态请求没回来之前禁用按钮。后端可以用订单号做唯一索引或者用 Redis 加锁。毕业设计里前端加个disabled就够用了但要知道后端幂等才是根本方案。5. 二次开发与进阶换数据库、加接口、做部署的实操建议把项目跑通只是第一步真正让它变成你的东西得动手改。我一般会从三个方向入手换数据库、加业务接口、做打包部署。换数据库这件事很多人觉得改个 URL 就行其实不止。如果你从 MySQL 换到 PostgreSQL驱动类名要改成org.postgresql.DriverURL 格式变成jdbc:postgresql://localhost:5432/mall_db分页拦截器的DbType也要改成DbType.POSTGRE_SQL。另外MySQL 的LIMIT语法和 PostgreSQL 的LIMIT ... OFFSET虽然像但 MyBatis-Plus 会自动适配不用手写。建表语句里的AUTO_INCREMENT要改成SERIAL或GENERATED BY DEFAULT AS IDENTITY。这些细节不改启动就报错。加接口的套路是固定的Controller 定义路由和参数 → Service 写业务逻辑 → Mapper 写数据库操作。比如加一个「商品收藏」功能先建favorite表然后写FavoriteController、FavoriteService、FavoriteMapper。Controller 里用RestController和RequestMapping注解方法参数用RequestParam或RequestBody接收。Service 里注入 Mapper用 MyBatis-Plus 的insert、delete、selectList等方法操作数据。Mapper 接口继承BaseMapperFavorite就自动有了基础 CRUD 方法不用手写 XML。打包部署这块后端用mvn package打成 jar前端用npm run build打成静态文件。部署时后端java -jar运行前端静态文件放到 Nginx 的html目录下Nginx 配置里加一个反向代理把/api开头的请求转发到后端端口。这样前端和后端就通过 Nginx 串起来了不用在代码里写死 IP 和端口。server { listen 80; server_name your_domain; location / { root /var/www/mall-frontend; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files这一行是给 Vue Router 用的防止刷新页面出现 404。proxy_pass把/api请求转发到后端注意末尾的/要不要加加了会去掉/api前缀不加会保留根据后端接口路径决定。验证部署是否成功最简单的办法是本地浏览器访问服务器 IP能打开页面点几个商品数据能加载登录、加购物车、下单走一遍不报错。如果页面能开但接口 404检查 Nginx 的proxy_pass和后端实际路径是否匹配。如果接口通了但数据不对检查数据库连接和 Nginx 转发的 header 有没有丢。从那以后我每次拿到一个前后端分离项目都强制自己先跑通「登录 → 查列表 → 加购物车 → 下单」这条最短链路再去看其他模块。这条链路通了说明环境、数据库、接口、跨域、鉴权这些基础环节都没问题剩下的就是业务逻辑的堆叠。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →