news 2026/10/7 21:59:23

SpringBoot+Vue3+MySQL商城毕设全流程:数据库脚本、联调与打包避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue3+MySQL商城毕设全流程:数据库脚本、联调与打包避坑

简介:这是一份面向毕业设计场景的商城购物网站完整源码包,基于Spring Boot、Vue 3.0与MySQL开发,同时支持前台用户购物与后台管理员运营,适合计算机相关专业学生直接参考、二次开发或作为课程设计、毕业答辩演示项目。系统涵盖用户注册登录、商品检索与购买、地址管理、订单发货、商品与分类维护、评论管理及系统设置等模块,前后台分工明确,并附带数据库脚本、管理账号及前后台启动地址,便于快速运行与理解完整业务流程。资源包共314个文件,以Java源代码、Vue组件、CSS样式、JavaScript脚本及XML配置为主,辅以SQL脚本、项目说明文档和少量图片资源,整体压缩后约910KB,结构较为精简。开发环境推荐Idea与Vscode,适合已掌握Java基础、希望快速搭建全栈项目的学习者。目前已有184人学习下载,可作为实战型毕设项目的重要参考,帮助熟悉商城类系统的权限设计、商品管理逻辑及前后端联调方式。

1. 一个商城毕设,凭什么能同时装下 SpringBoot、Vue3 和 MySQL?

“基于 SpringBoot+Vue3.0+MySQL 开发的商城购物网站”,我第一次拿到这类毕业设计题目时最关心的不是页面好不好看,而是三件事:Java 源代码能不能直接编译,数据库脚本能不能一次导入,前后端联调需要动哪些配置。这类项目之所以在毕设里经久不衰,是因为它覆盖了从数据库设计、后端接口、前端交互、打包部署的完整链路,又不至于复杂到一对一做不完。适合目标是用最短时间跑通、同时能在答辩现场掰开讲清楚的人。后面我会按我自己的落地顺序,把数据库脚本、SpringBoot 配置、Vue3 联调、静态资源整合和踩坑记录一条条过完。

2. 从数据库脚本到 SpringBoot 工程:把「会跑」变成「看得懂」的起点

很多拿到代码的同学第一步就双击运行,结果启动失败就慌了。我会先把数据库脚本和项目结构对应着看,因为这套商城是典型的表驱动业务:你有哪几张表,Controller 才能提供哪几个接口。源码里通常自带 sql 文件夹,不要跳过它直接改代码。

2.1 先分清商城的四个核心模块:用户、商品、购物车、订单

商城业务看起来功能多,但毕业设计最核心的链路只有一条:用户登录后浏览商品,把商品加购物车,购物车结算成订单,订单完成后扣减库存。围绕这条链路,数据库至少要设计四张业务表:user、product、cart、order,然后再加上 order_item 存订单快照,避免商品改价后历史订单金额变掉。能讲清楚这张表关系,答辩时一半问题就已经答完了。

一个常见做法是先把建库脚本在本地执行,再打开实体类看字段。下面是一个最简的建库脚本片段:

CREATE DATABASE IF NOT EXISTS mall_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE mall_db; CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(255) NOT NULL COMMENT 'BCrypt 或 MD5+盐', `nickname` varchar(50) DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `product` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `title` varchar(200) NOT NULL, `price` decimal(10,2) NOT NULL, `stock` int(11) NOT NULL DEFAULT '0', `image` varchar(500) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里有几个参数值得记住。utf8mb4 比 utf8 多存了 emoji 和生僻字,商城商品描述里很容易出现特殊字符,所以建库和建表都要用它。AUTO_INCREMENT 是自增主键,MyBatis-Plus 里对应的 @TableId(type = IdType.AUTO) 能直接映射。password 字段给到 255 不是因为密码长,而是因为 BCrypt 加密后是 60 位左右,将来换成多加密算法也没压力。unique key 保证用户名不能重复,Controller 层校验时就不用先查一遍再插入了。

订单表需要单独说,因为订单号的生成方式在答辩时常被追问。最简单可靠的是用数据库自增 id 加日期前缀,比如 ORDER2025060710010001,不要用随机 UUID 当订单号给用户看,可读性太差。表里需要冗余一份商品名称和单价快照,订单详情页要展示的是下单那一刻的商品信息,而不是当前商品表的信息。这种设计细节虽然不体现核心代码,但能证明你理解业务实体。

2.2 导入数据库脚本:命令行和可视化工具都要会

拿到 .sql 脚本后,我一般不用双击数据库客户端执行,因为很容易因 IDE 默认连接库不对而出错。更稳的方式是在 MySQL 命令行里指定目标库执行。如果脚本开头没有 CREATE DATABASE,就手动先建库:

mysql -u root -p mysql> CREATE DATABASE IF NOT EXISTS mall_db DEFAULT CHARACTER SET utf8mb4; mysql> USE mall_db; mysql> SOURCE /path/to/mall.sql;

如果是第一次接触 MySQL 安装配置,先保证 mysql 命令能被终端找到。Windows 上装完 MySQL 8.0 后,需要在系统环境变量里把 MySQL 安装目录下的 bin 加进 PATH,否则会提示 mysql 不是内部或外部命令。这个步骤和“mysql 安装教程”里常写的是一样的,但和导入脚本是两个环节,别混在一起排查。

如果习惯用 Navicat 或 DataGrip,导入时要注意连接信息里的编码。连接属性里设置“使用 MySQL 字符集”,并在高级设置中选 utf8mb4,否则脚本里即使写了 utf8mb4,客户端以系统默认 GBK 传输时也可能乱码。如果你是在 IDEA 的 Database 面板里手动导出的脚本,导出选项也要明确选 UTF-8,否则注释和中文默认值会在第一步就坏掉。

不论用哪种方式,导入完成后都建议跑一条查询验证表数量和数据量:

SELECT COUNT(*) FROM product;

如果脚本里有测试数据,这条查询应该能返回几十条商品记录。如果返回 0,先别急着写接口,因为前端页面可能空荡荡,你很难判断到底是对的还是错的。数据是商城演示效果的底子,一张空表会让前端展示、搜索、分页全部失去意义。

2.3 SpringBoot 工程结构与 application.yml:认识自动配置的入口

拿到 Java 源码后先看 Maven 工程结构,不用细读每个类,只要确认三层分包在:controller 负责接收参数和返回结果,service 负责业务规则,mapper 负责 SQL 访问。就算你还没读代码,答辩时也能顺着这个结构讲清一条请求怎么走。下面是一个典型的目录骨架:

mall-api ├── pom.xml ├── src/main/java/com/example/mall │ ├── MallApplication.java │ ├── controller │ │ ├── UserController.java │ │ ├── ProductController.java │ │ ├── CartController.java │ │ └── OrderController.java │ ├── service │ │ ├── UserService.java │ │ └── impl/UserServiceImpl.java │ ├── mapper │ │ ├── UserMapper.java │ │ └── ProductMapper.java │ └── entity │ ├── User.java │ └── Product.java └── src/main/resources ├── application.yml └── mapper/*.xml

如果这里用的是 mapper 接口加 XML,那么 application.yml 里的 mybatis.mapper-locations 必须指向实际路径,否则启动时会报 Invalid bound statement (not found)。如果项目用了 MyBatis-Plus,这个问题会被 BaseMapper 隐藏一部分,但自定义 SQL 仍然要对应 XML。

配置文件是项目的黑匣子,很多启动失败最后都发现在这里。一段常见的数据源配置如下:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/mall_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 10 minimum-idle: 2 connection-timeout: 30000 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true type-aliases-package: com.example.mall.entity

连接地址里最容易翻车的是 allowPublicKeyRetrieval=true 和 useSSL=false。MySQL 8 默认使用 caching_sha2_password 认证,JDBC 驱动第一次连接时拿不到公钥就会报 Public Key Retrieval is not allowed,尤其在你用旧版 MySQL 驱动时。加了 allowPublicKeyRetrieval=true 能直接解决,但生产环境要谨慎,这个参数会允许客户端请求公钥,一般在本地开发和毕设演示场景用没问题。

HikariCP 是 SpringBoot 默认的连接池,不需要额外引入。maximum-pool-size 不是越大越好,商城演示场景 10 个连接足够,连接池太大会占用 MySQL 后端连接数,压测时反而把数据库拖垮。minimum-idle 设为 2 可以理解为始终保持两个连接在待命,降低首次请求延迟。connection-timeout 是拿连接的最大等待时间,超过 30 秒就报错;如果你发现偶尔请求卡住并伴随连接超时异常,优先怀疑是不是慢 SQL 把连接占光了。

这一段还要提一下 SpringBoot 自定义自动配置:你不需要自己写配置类绑定 DataSourceProperties,只要引入 spring-boot-starter-jdbc,SpringBoot 就会根据 classpath 和 application.yml 自动装配数据源。这也是为什么改 url、driver-class-name 后不需要改 Java 代码的原因。如果将来做复杂项目,可以在配置类上使用 @ConfigurationProperties(prefix = "spring.datasource") 扩展参数,但毕设阶段不要动自动配置,除非你已经看懂了 spring.factories 那套机制。

3. Vue3 前端联调:从 Vite 代理到「vue 打包放进 SpringBoot」的静态资源整合

Vue3.0 这层是很多人跑不起来的地方,因为前后端的启动顺序、端口、接口路径一旦没对齐,页面就会一直转圈。要明确一点:开发环境前端是独立运行的,后端也是独立运行的,两者通过 HTTP 通信,不存在“一个项目包含前端源码”这种说法。前端源码通过 npm 管理依赖,后端通过 Maven 管理依赖,这是两套体系。

3.1 Vite 开发服务器代理:让 /api 请求不再跨域

Vue3 工程用 Vite 启动后默认端口是 5173,SpringBoot 是 8080。如果前端直接用 axios 请求 http://localhost:8080/api/product/list,浏览器会因为端口不同直接拦截跨域请求,哪怕后端配了 CORS,开发环境操作起来也别扭。更干净的做法是在 Vite 的 server.proxy 里配置代理,让前端代码只请求相对路径 /api。

在项目根目录的 vite.config.js 中加入下面的配置:

import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } } })

配置里最关键的是 rewrite。如果后端 Controller 的 RequestMapping 本身就是 /api/product/list,就不需要 rewrite,只需 target 指向后端。如果后端接口是 /product/list,前端却习惯用 /api 开头,那 rewrite 会把 /api 前缀去掉。到底要不要 rewrite,直接决定接口能不能调到,这是联调最常踩的坑之一。判断方法很简单:启动前后端后,在浏览器地址栏访问 http://localhost:5173/api/product/list,如果返回 JSON 就说明代理通了,如果返回 404 就去查 rewrite 或者后端实际路径。

还有一点,changeOrigin 要设为 true,它会把请求头里的 Host 改写成目标地址的 Host,很多后端框架如果校验 Host,不做这一步就会报 403。这套配置改完后不需要重启 SpringBoot,只需要让 Vite 重新加载配置。

3.2 API 封装和登录态:前端怎么记住用户

商城系统几乎所有页面都要依赖“当前登录用户”,前端不能每调一次接口就输入一次密码,所以常见做法是登录成功后把 token 存 localStorage,后续每次请求自动带在请求头里。Axios 拦截器是最简单的统一处理点。

下面是我常用的请求封装:

import axios from 'axios' import router from '../router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('mall_token') if (token) { config.headers.Authorization = 'Bearer ' + token } return config }) request.interceptors.response.use( response => response.data, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('mall_token') router.push('/login') } return Promise.reject(error) } ) export default request

baseURL 设为 /api,配合上一节的 Vite 代理,开发环境就不会有绝对地址写死的问题。拦截器里 token 没有就放行,因为登录接口本身是不需要 token 的;如果某些接口需要登录但漏带了,后端返回 401,前端统一跳回登录页,而不是在每个页面里单独写逻辑。timeout 设为 10 秒,数据库慢或后端卡住时用户不会一直等。

在 Vue 组件里调用时,不要直接 import axios 创建新实例,而是统一用这个封装。这样将来如果要改成 refresh token,或者要记录请求耗时,只需要动一个文件。这个细节在答辩时拿出来很有价值,说明你考虑了维护成本。

3.3 打包整合:vue 打包放进 SpringBoot 的两种选择

毕设最后需要把前端嵌入后端,变成可以直接运行的一个 jar。最省事的做法是执行前端构建,把 dist 里的静态文件复制到 SpringBoot 的 static 目录,重新打包。先在前端工程执行:

npm run build

构建完成后,前端工程下会生成 dist 目录,里面有 index.html 和 assets 文件夹。然后把这些文件复制到后端项目的 src/main/resources/static 下。SpringBoot 对 static 目录有默认的静态资源映射,所以访问 http://localhost:8080/ 时,直接返回 index.html,无需再写 Controller。

复制后要重新执行 Maven 打包:

mvn clean package -DskipTests

这里必须用 clean,否则旧的 static 资源会残留在 target 里,导致你改了页面但 jar 里还是旧版。这是很常见的前端改了没效果的原因。

除了一刀切复制,还有一种情况是只让 SpringBoot 接管特定路径。如果用了前端路由的 history 模式,URL 可能是 /product/1 这种直接访问,SpringBoot 默认找不到这个 Controller 路径,会返回 404。可以加一个简单的转发 Controller:

@Controller public class IndexController { @RequestMapping(value = {"/", "/index.html"}) public String index() { return "forward:/index.html"; } }

但这个方法只能兜底根路径,不能覆盖所有 history 路由。更通用的做法是注册一个 ViewController 或使用 WebMvcConfigurer 重写 addViewControllers,把所有没有静态文件的路径转发给 index.html。另一个省事的方法是前端路由改用 hash 模式,URL 变成 #/product/1,每次刷新时实际请求的仍是根路径,就不存在 404。毕业设计演示环境中,hash 模式的可读性虽然不如 history,但不容易出幺蛾子。

4. 基于 SpringBoot 的七条必调参数:从数据源到 MyBatis 的 SQL 输出

很多现成源码能跑,但答辩老师一改端口、一改数据库密码就崩,说明配置写死了。我倾向于在做项目时把所有环境相关的参数集中到 application.yml,并且知道每条参数影响了什么。下面这条参数清单,是我觉得商城里必调的七项。

4.1 数据源与连接池:先看清 URL 和 HikariCP

数据源配置是商城项目启动的第一道门槛。jdbc 连接串里那串参数不是随便抄的,useUnicode=true 和 characterEncoding=utf8 保证中文传输不乱码;serverTimezone=Asia/Shanghai 避免 Docker 或云服务器时区不同导致时间相差 8 小时;allowPublicKeyRetrieval=true 解决 MySQL 8 的认证兼容。这串参数看着长,少了某一个就会遇到一个莫名其妙的连接异常。

连接池部分,HikariCP 三个参数建议显式写出来:maximum-pool-size、minimum-idle、connection-timeout。写出来的意义是让答辩老师知道你不是只会默认值。连接池如果太小,压测时会出现 connection is not available;如果太大,数据库会拒绝连接。一个经验值是单机 MySQL 下,maximum-pool-size 8-15 够用,不要把连接池调成 50。

隐藏坑是连接池与事务超时叠加。如果某个订单事务里执行了远程调用或者循环写库,事务会把连接持有很长时间,超过 connection-timeout 后,其它线程拿不到连接就报超时。这时候改 connection-timeout 只是掩盖问题,应该去看慢 SQL 和事务边界。商城项目里最容易出现这个场景的是下单时先锁库存、再扣积分、再通知物流,每一步都可能慢。方案是缩小事务,只在真正写操作的开头开事务。

另外,商城库存扣减时还会遇到 MySQL 锁的问题。悲观锁用 SELECT ... FOR UPDATE,乐观锁用 version 字段,在并发下单时这两者的表现完全不同。用 FOR UPDATE 实现简单但会把行锁一直持有到事务结束;乐观锁适合写冲突少的场景。这些内容虽然和数据源配置不直接相关,但属于同一层的“数据库参数”问题,我一般会把它和连接池放在一起准备,避免答辩时被问倒。

4.2 MyBatis 驼峰映射与 SQL 日志:排查字段为 null 的前提

后端接口返回字段是 null 的时候,十有八九是数据库下划线列名没有映射到实体类。在配置里开启 map-underscore-to-camel-case: true 后,product_title 会自动映射到 productTitle,不需要写一堆 @Results。这是一个成本最低但收益极高的配置。

为了看到 SQL 执行情况,还可以在数据源配置同级加上 MyBatis 日志:

mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

这样每次执行 SQL,控制台会打印完整的输入参数和输出结果。开发环境开着没问题,演示前建议去掉或改成 warn,因为日志输出会拖慢接口,也会让别人从控制台看到你执行了哪些 SQL。

如果项目用的是 MyBatis-Plus,实体类和表之间的对应关系变得很关键。看下面这个实体:

@TableName("product") public class Product { @TableId(type = IdType.AUTO) private Long id; private String title; private BigDecimal price; private Integer stock; }

MyBatis-Plus 会自动把驼峰转下划线生成 SQL,比如 stock 直接对应 stock 列。如果你想根据实体类自动生成建表 SQL,可以用 MyBatis-Plus 的 SchemaTool 或数据库工具类生成 DDL,但毕设阶段不推荐依赖这个功能,因为表已经建好了,让代码自动建表反而可能出现列类型不一致。手动执行 SQL 脚本更可控,这也是标题里特意写“内含数据库脚本”的原因:脚本是保证可复现的一部分。

4.3 SpringBoot 版本太高:换依赖还是换 JDK

毕设代码下载下来第一件事要看 pom.xml 里的 spring-boot-starter-parent 版本。如果是 SpringBoot 3.x,必须搭配 JDK 17 和 MySQL 驱动 8.x;如果是 SpringBoot 2.x,JDK 8 就够了。许多同学本机只有 JDK8,直接编译 3.x 项目会报 Unsupported class file version。不要硬装新 JDK,先确认代码用的 SpringBoot 版本,再决定要不要降版本。

SpringBoot 3.x 另一个大坑是 javax.servlet.* 全部换成了 jakarta.servlet.*。如果你的源代码里用了 javax 开头的导入,并且引用了 Tomcat 10 等新容器,编译会直接失败。解决方式要么升级代码导入,要么把 SpringBoot 版本降到 2.7.x。一般毕设商城逻辑简单,降版本更容易,只是要注意 MyBatis 的 starter 版本也要配套:SpringBoot 2.x 用 mybatis-spring-boot-starter 2.2.x,SpringBoot 3.x 用 3.0.x,混用会报 SqlSessionFactory 找不到。

想快速查看当前项目版本,可以在 pom.xml 所在目录执行:

mvn help:evaluate -Dexpression=project.version -q -DforceStdout

这条命令会把项目版本打印到终端,不需要打开 IDE。这个动作可以让你对源码的依赖现状心里有数,答辩时也能说清楚为什么这个项目不能用 Java 8 编译。

4.4 日期、文件和端口:三项默认值不够用的配置

商城后台通常要上传商品图片,如果 SpringBoot 默认文件大小限制不调,前端传一张 5MB 商品图就报 413。需要设置 spring.servlet.multipart.max-file-size 和 max-request-size。日期格式化也要统一,否则后端返回的 LocalDateTime 序列化成数组或 ISO 字符串,前端解析会出问题。设置 jackson date-format 后,接口返回的时间字符串会统一变成 yyyy-MM-dd HH:mm:ss。端口可以在 application.yml 固定,但启动时用 --server.port 覆盖更好,方便同一套代码多个实例演示。

参数位置一句话作用
spring.datasource.url连接串控制编码、时区、MySQL8 公钥认证等行为
spring.datasource.hikari.maximum-pool-size连接池防止演示流量把连接耗尽
mybatis.configuration.map-underscore-to-camel-caseMyBatis下划线字段自动映射驼峰
mybatis.configuration.log-implMyBatis控制台输出 SQL 与参数
spring.servlet.multipart.max-file-size上传商品图片能传到 5MB 以上
spring.jackson.date-formatJSON统一接口返回的时间格式
server.portWeb 服务后端端口,启动时可覆盖

以上七条按优先级排,前四条不配好项目根本跑不通,后三条不配好演示时会丢分。把它们逐条在 application.yml 里过一遍,比读一天源码更有用。

5. 商城系统避坑指南:数据库脚本、跨域和打包的 5 个翻车现场

下面这五个问题,几乎每个做商城毕设的人都会碰到至少一个。每条按现象、原因、解决写清楚,方便你对照排查。

5.1 数据库脚本导入后中文变成问号:编码三件套没对齐

现象:执行商城.sql 后,后台商品名称变成“???”或者“锟斤拷”,英文正常,只有中文损坏。

原因:sql 文件本身可能是 UTF-8 编码,但 MySQL 客户端连接字符集不是 UTF-8。Windows 命令行默认可能是 GBK,Navicat 的数据库连接字符集也可能是 Latin1。

解决:先确认文件编码,用编辑器看右下角是否 UTF-8。命令行导入加 --default-character-set=utf8mb4:

mysql -u root -p --default-character-set=utf8mb4 mall_db < mall.sql

如果是 Navicat,右键数据库选择“导入 SQL 文件”前,在连接属性高级里把 encoding 设为 utf8mb4。导入后再查一次数据是否正常。如果已经乱码,不要直接改数据,最干净的方案是 drop 掉重建数据库,重新导入。这个处理成本低,但犹豫只会让后续联调越来越难排查。

5.2 SpringBoot 连不上 MySQL 8:Public Key Retrieval 报错

现象:启动项目后接口 500,控制台出现 ResultSet 相关异常,错误信息里有 Public Key Retrieval is not allowed,或者 SSL 连接错误。

原因:MySQL 8 默认认证插件 caching_sha2_password,Java 连接器需要先获取服务器公钥验证。如果使用 useSSL=false,连接器默认不主动取公钥,于是拒绝连接。

解决:在 JDBC URL 加 allowPublicKeyRetrieval=true&useSSL=false。注意这两个参数必须和前面其他参数用 & 分隔,且整个 URL 要放在引号里。如果你用的是 application.properties 而不是 yml,要写成:

spring.datasource.url=jdbc:mysql://localhost:3306/mall_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true

本地演示这样改没问题,生产环境则应该使用 SSL 连接并妥善配置证书,不能简单关闭 SSL。这是安全底线,也避免答辩老师借题发挥。

5.3 Vue history 模式刷新 404:静态资源整合后遗症

现象:前端打包放进 SpringBoot 后,从首页点击进入商品详情页正常,但用户直接访问 /product/detail/1 就 404;刷新页面也 404。

原因:前端路由是 history 模式,URL 是真实路径请求到 SpringBoot 后端,而 SpringBoot 没有对应资源映射,只认 /static 下的文件,所以无法返回 index.html。

解决:要么改前端路由为 hash 模式,要么后端加 SPA fallback。最简单的 hash 模式改法是在 createRouter 时将 createWebHistory() 改为 createWebHashHistory(),一行代码搞定。如果需要保留 history 模式,就在后端加一个 /{path:[^\.]*} 的转发 Controller 或 WebMvcConfigurer,让非静态资源路径全部返回 index.html。注意这个 fallback 只服务于前端路由,别干扰 /api 接口;用正则排除带点的路径,避免 CSS/JS 也落到转发。

5.4 CORS 配置和 Vite 代理重复:请求被两次跨域处理

现象:开发环境前端通过代理访问后端,后端也写了 @CrossOrigin 或 CorsFilter,但某些浏览器请求仍然 405,或者 OPTIONS 预检请求直接失败。网上说法五花八门,越调越乱。

原因:浏览器在同一上下文中同时触发两层跨域逻辑。前端请求先被 Vite 代理转发到 http://localhost:8080,这时代理已经让浏览器认为请求是同源的,不会产生跨域。但后端如果配置了 CorsFilter 允许来自 http://localhost:5173,那么当代理请求又带上了 Origin 头,Spring 的过滤链可能处理重复 CORS 头,导致预检冲突。

解决:开发环境中代理和 Spring CORS 只留一个。我一般做法是:开发环境只配 Vite 代理,后端不写任何 @CrossOrigin;生产环境前后端同源部署在同一个 SpringBoot 进程里,也不存在跨域。只有当前后端分离部署在不同域名时,才需要认真配置后端 CORS,而且要指定允许的 Origin、方法和 Headers,不要用“*”加 allowCredentials 同时开。这个问题排查时最怕的是同时打开多个工具改来改去,建议先全局搜代码里所有 @CrossOrigin 和 CorsFilter,统一方案后再测。

5.5 打包后前端资源不更新:target 里的旧文件没清理

现象:改了商品标题,重新 npm run build 并覆盖 static 后,运行 jar 打开页面标题还是旧值;IDE 里看文件已经是新的。

原因:Maven 打包时如果资源拷到 target/classes/static 下,而 target 中保留着上一次构建的旧资源,如果没有 clean,Maven 可能不会清空整个 target,旧 index.html 和 assets 被保留下来。

解决:后端打包前强制 clean。建议按这个顺序执行:

npm run build cp -r dist/* ../mall-api/src/main/resources/static/ cd ../mall-api mvn clean package -DskipTests

如果还是旧资源,检查 static 目录下是否残留了旧的 asset 文件名。Vite 构建时文件名通常带哈希,index.html 引用的是新哈希文件,而老的哈希文件还在时浏览器可能从缓存取。可以给 index.html 加 no-cache 或部署前清理 target 目录。演示前不要开浏览器缓存,最好用无痕窗口,避免把时间浪费在缓存上。

6. 让毕设项目更「值钱」:三个能写进答辩的验证手段

商城项目跑通只是起点,能证明它经得起验证才是加分项。下面三个手段成本都很低,但对答辩和实际交付很有说服力。

6.1 用 IDEA HTTP Client 存一份接口回归文档

我习惯在工程里放一个 http 文件,把登录、商品列表、加购物车、下单这些核心接口的请求体保存下来。比如登录接口:

### 登录 POST http://localhost:8080/api/user/login Content-Type: application/json { "username": "test", "password": "123456" }

这个文件随手点运行就能测,比打开 Postman 一步步填参数要快。答辩演示时,现场用这个文件过一遍核心接口,能直观证明系统是联通的。

6.2 用 JMeter 做一次 50 线程的商品列表压测

JMeter 可以用来生成接口支持多少并发的证据。添加线程组,线程数 50,Ramp-Up 5 秒,循环 10 次,加 HTTP 请求指向商品列表接口,再加聚合报告和响应时间曲线。压测后主要看错误率、平均响应时间和吞吐量。商城演示场景,错误率为 0、平均响应时间 200ms 以内就算合格;如果出现大量 connection is not available,回去调连接池参数。

压测前先关闭 MyBatis 的 stdout 日志,否则日志同步写会拖慢接口,得到的结果不能真实反映代码性能。这是我吃过亏的地方,测了半天以为是数据库慢,结果罪魁祸首是控制台刷 SQL 日志。

6.3 用 Maven 打包后的 jar 做一次独立部署验证

最终演示不能只用 IDE 启动,我一般会先执行 mvn clean package,然后直接命令启动:

java -jar target/mall-api-0.0.1-SNAPSHOT.jar --server.port=8080

启动后打开 http://localhost:8080,前端页面由 SpringBoot 提供,后端接口也同源,全程不依赖前端 dev server。这个验证能证明你的代码不是只在 IDE 里能跑,也保证了交付给老师的是一份可以复现的完整产物。如果端口被占用,换下一个端口即可;前提是前端已经把接口请求写成相对路径,而不是写死 8080。

我自己的习惯是每次演示前一天做一次从数据库脚本到 jar 的 clean 全流程,因为很多昨天还能跑的翻车,其实都是环境残留或依赖被改过。这套验证方法跑完,至少能保证你的商城项目在答辩评委电脑上也能顺利启动。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 21:55:57

Python虚拟环境venv实战指南:从原理到最佳实践

你可能也经历过这种场景&#xff1a;照着教程往全局环境里装了一堆 Python 包&#xff0c;然后打开另一个项目&#xff0c;突然发现某个库从能用变成了报错。查了半天&#xff0c;不是代码写错了&#xff0c;是依赖打架了。Python 虚拟环境&#xff08;venv&#xff09;就是为了…

作者头像 李华
网站建设 2026/10/7 21:53:52

用Claude Code打造AI营销技能流水线:SEO与CRO自动化实战

1. 从"marketingskills"这个标题说起&#xff1a;它到底想解决什么问题第一次看到"marketingskills"这个标题&#xff0c;我脑子里冒出来的第一个念头是&#xff1a;这大概率不是一个单纯的营销课程合集&#xff0c;而是一套把营销能力"技能化"、…

作者头像 李华
网站建设 2026/10/7 21:51:30

Winsoft PDFium组件套件:Delphi/C++Builder源码级PDF引擎

简介&#xff1a;这是一套面向Delphi与C Builder开发者&#xff08;兼容5至10.3版本及Lazarus 2.0.6&#xff09;的PDF功能增强组件库&#xff0c;基于Google开源PDFium渲染引擎&#xff0c;支持PDF文档的高效查看、页面导航、文本提取与内容编辑&#xff0c;适用于桌面端PDF工…

作者头像 李华
网站建设 2026/10/7 21:48:43

Spring Boot考勤系统全栈开发实战:从数据库设计到部署避坑

1. 项目概述&#xff1a;从零搭建一套能用的考勤系统&#xff0c;到底难在哪先聊点实在的。提起“员工考勤系统”&#xff0c;很多人第一反应是“这不就是个打卡记录吗&#xff0c;有什么好做的”。但真正接过这类需求的人都知道&#xff0c;考勤系统最麻烦的从来不是打卡本身&…

作者头像 李华
网站建设 2026/10/7 21:47:00

Flutter鸿蒙化适配:如何用分层结构重构analysis_options配置

做 Flutter 鸿蒙化适配的团队&#xff0c;基本都会撞上同一个尴尬场景&#xff1a;把三方库拉到鸿蒙 SDK 工程里&#xff0c;跑一遍flutter analyze&#xff0c;屏幕上几千条 warning 和 info 刷下来&#xff0c;一半是“平台差异”造成的误报&#xff0c;另一半却是真问题。本…

作者头像 李华