SpringBoot+Vue网上购物商城系统全栈开发实战:从数据库设计到源码交付
做网上购物商城系统这件事很多人第一反应是满大街都是的项目但真正动手之后才会发现能把一个商城项目从数据库设计、后端接口、前端页面到可交付的源码文档完整跑通不是一件轻松的事。这几年我在毕业设计和企业实训里看过不少SpringBootVue的商城项目最常见的两个极端是要么功能堆得很满但代码乱成一团要么代码能跑但文档和交付物基本是摆设换一台电脑就起不来了。这篇东西我就以自己带项目、改项目的实战经验出发把一个网上购物商城系统从功能规划到技术选型、从数据库建模到核心接口、从前端工程到源码文档组织完完整整地拆开说一遍。无论你是准备拿它做毕业设计还是想通过全栈项目入门找工作这套思路都能直接复用。1. 项目功能边界先想清楚商城要做什么再动手1.1 商城项目的典型拆法前台展示与后台管理网上购物商城系统听起来是一个整体实际开发时必须拆成两个逻辑域面向普通用户的商城前台以及面向管理员或运营人员的后台管理端。这个拆分不是拍脑袋而是由两端的访问入口、操作频率和数据安全级别决定的。前台的核心是浏览—决策—下单用户进来之后要能够快速找到商品、看清详情、加入购物车、完成结算然后查看自己的订单。后台的核心是维护—处理—追踪管理员要能上架商品、调整分类、处理订单状态、管理用户信息。这两端的交互模型完全不一样前台是大量只读请求加少量写操作后台是集中式的增删改查所以从项目结构设计的第一天起就应该把它们当作两个独立的子系统来规划。1.2 用户端功能清单一个标准的用户端至少需要覆盖以下功能点注册与登录用户名/手机号密码的注册登录登录后使用Token维持会话状态首页与商品浏览轮播图展示、商品分类导航、热门商品推荐商品列表与搜索按分类筛选、按关键词搜索、分页加载商品详情多图展示、价格库存信息、商品参数描述购物车加入商品、修改数量、勾选结算、删除单项订单流程从购物车生成订单、选择收货地址、创建订单、查看订单列表与详情收货地址管理新增、编辑、删除、设置默认地址有些项目还会加上订单取消、确认收货、商品评价等操作。评价功能会牵扯到另一套表结构和接口逻辑如果你的时间有限我建议把它列为二期功能而不是死磕在第一版里。第一版商城项目最忌讳的就是贪大求全。1.3 管理端功能清单管理端相比之下要朴素得多核心是四个模块商品管理商品的添加、编辑、上下架、删除需要支持图片上传分类管理商品分类的树形展示与维护订单管理查看全部订单、按状态筛选、处理发货操作用户管理用户列表展示、启用/禁用账号权限控制上不需要做得太复杂一个角色管理员就够。如果做了多角色就需要引入Spring Security的RBAC模型这会让项目复杂度上一个台阶纯粹做商城主体功能的话可以不用考虑。1.4 边界定义明确哪些功能故意不做这一点特别想拿出来单独讲因为很多同学做项目时会不由自主地给自己加戏。支付接口要不要接优惠券和秒杀要不要做分布式会话要不要上这些都不是第一版该做的事情。真实支付接口需要商户资质个人开发和毕设环境根本无法落地正确的做法是下单时生成一个待支付状态的订单留出支付回调的接口约定就可以了。秒杀和优惠券涉及库存超卖、限购策略、活动配置等一系列高并发问题这些内容适合作为进阶专题硬塞进一个单体项目里只会让代码失控。先做一个功能完整、逻辑闭合、可展示可答辩的版本远比一次堆十个模块更有竞争力。1.5 初学者的功能裁剪建议如果你的时间只有两到三周我建议砍掉评价模块砍掉商品多规格比如颜色、尺码把商品做成单一SKU然后集中精力把购物车和订单这两条核心链路做扎实。这两条链路牵涉到的数据流转最复杂也最能在面试和答辩中展示你的设计能力。2. 为什么是SpringBootVue选型逻辑与版本适配2.1 后端选SpringBoot的理由SpringBoot在Java后端项目里的地位已经不需要多解释了它在Spring框架的基础上通过自动配置和Starter机制大幅降低了起步成本。做商城这种典型的CRUD加业务流转的系统SpringBoot提供了足够完整的生态支持内置Tomcat让部署变得极其简单打一个Jar包就能跑这对项目交付来说价值巨大。我实际开发中最看重的两点一个是MyBatis-Plus或者Spring Data JPA这类持久层框架与SpringBoot的整合基本零成本另一个是SpringBoot的测试和调试体验比传统SSH方案好得多。做商城项目时你不需要在XML配置上浪费任何时间所有注意力都可以集中在业务逻辑本身。2.2 前端选Vue的理由Vue的核心优势是组件化和响应式数据绑定商城前台的商品卡片、购物车列表、订单状态标签都是天然的组件化场景。Vue2配合ElementUI、Vue3配合Element Plus组件库直接把后台管理系统需要的表格、表单、弹窗、分页全部覆盖掉了。为什么不用JSP这样的服务端模板或者干脆前后端不分离因为商城系统的前台交互是有状态的购物车数量的实时更新、登录状态的全局维护、商品筛选的即时反馈这些交互模式用前后端分离的方式做要舒服得多。更重要的是前后端分离的项目结构本身就是行业主流练手的同时也是在适应真实的工作模式。2.3 SpringBoot版本和JDK版本的匹配问题关于springboot版本太高这个搜索热词我得重点说几句。现在网上的教程和源码鱼龙混杂有讲SpringBoot 2.3的有讲2.7的还有直接上3.x的很多人照着教程敲代码结果启动直接报错十有八九是版本不匹配。我的建议很明确新手首选SpringBoot 2.7.x JDK 8原因很简单资料最多、踩坑最少、绝大多数第三方依赖都能兼容如果你的电脑已经装了JDK 17那可以选SpringBoot 3.x但要注意3.x底层是Jakarta命名空间很多老教程里的javax包路径要跟着改MyBatis-Plus选与SpringBoot版本对应的分支版本2.7配3.5.x3.x配3.5.3以上表格最能说明问题我自己常用的组合是组件版本选择备注JDK8 或 172.7配83.x配17混用会启动失败SpringBoot2.7.x 或 3.x新手优先2.7.xMyBatis-Plus3.5.x与SpringBoot版本匹配即可MySQL5.7 或 8.08.0更推荐Node.js14/16Vue216/18Vue3版本过低或过高都可能导致构建失败2.4 前端环境配置的几个老坑很多做商城项目的同学不是死在Java代码上而是死在Vue环境搭建这一步。vue安装及环境配置这个搜索词长年热度不减就是因为这里面的坑实在太经典。第一个坑是npm下载慢。解决方案是切换淘宝镜像源在项目根目录创建.npmrc文件写入registryhttps://registry.npmmirror.com执行npm install的时间能从十分钟压缩到一两分钟。第二个坑是node-sass。Vue2项目里如果直接装node-sass经常因为本机Node版本过高编译失败。现在的主流做法是改用sassdart-sass开发依赖里安装的是sass而不是node-sass兼容性要好很多。如果项目里已经锁定了node-sass那就要保证Node版本在14左右。第三个坑是Vue CLI和Vite的选择。Vue2标配Vue CLIwebpackVue3可以用Vite。Vite启动速度快但有些老插件可能不兼容。做商城项目这种中规中矩的应用Vue CLI Vue2的路线最稳Vue3 Vite的路线上手也不难看你自己更愿意投入哪套生态。3. 数据库建模商城项目里最不能省的一步3.1 核心表清单与关系总览商城系统的数据库一般由七张核心表构成用户表、商品分类表、商品表、购物车表、订单表、订单项表、收货地址表。如果加了轮播图管理再补一张轮播图表如果做了商品评价再补一张评论表。这个规模对单体项目来说刚刚好既能展示数据库设计能力又不至于把精力耗在无意义的表结构上。表与表之间的关系并不复杂分类表与商品表是一对多用户表与购物车表是一对多用户表与订单表是一对多订单表与订单项表是一对多用户表与收货地址表是一对多。搞清这五组关系之后外键要不要在数据库层面强制约束这件事我个人的做法是逻辑层面维护关联物理外键只在必须保证强引用完整性的场景才加因为商城项目后续如果要拆分表或做分库分表物理外键会成为障碍。3.2 关键表的字段设计要点用户表的核心字段包括id、username、password、nickname、phone、avatar、status、create_time。password字段直接存MD5是绝对不行的至少要做加盐处理这个我放到后端接口部分细说。商品分类表最常见的结构是id、parent_id、name、level、sort其中parent_id是指向自身id的外键用来支持二级分类。比如手机数码是顶级分类智能手机是它的子分类。商品表是整个商城的信息中枢字段设计上要特别注意以下几点price字段使用decimal(10,2)而不是double浮点数在金额计算上会有精度丢失这是金融领域也要求必须规避的问题stock字段用int每次下单扣减库存时要在SQL层面做条件更新防止超卖main_image保存单张主图URLdetail_images可以用逗号分隔的字符串存储status字段用tinyint表示上架/下架状态0下架1上架加一个sales字段记录销量方便前台按销量排序购物车表的核心字段是id、user_id、product_id、quantity、checked。checked字段表示这条购物车记录在结算时是否被勾选这个字段很多教程都会漏掉但实际操作中用户不可能每次结算都把购物车清空勾选状态必须持久化。这里给一个商品表的参考SQL片段CREATE TABLE product ( id bigint(20) NOT NULL AUTO_INCREMENT, category_id bigint(20) DEFAULT NULL, name varchar(200) NOT NULL, subtitle varchar(500) DEFAULT NULL, main_image varchar(500) DEFAULT NULL, detail_images text, price decimal(10,2) NOT NULL, stock int(11) NOT NULL DEFAULT 0, sales int(11) NOT NULL DEFAULT 0, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 0下架 1上架, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;3.3 订单表与订单项表为什么必须拆开这一节专门讲一个经典的建模问题。有些刚接触的项目会把用户买的所有商品直接拼成一个字段塞进订单表里比如存成商品A x2商品B x1这种字符串。看起来很省事但一旦需要统计某个商品的销量、分析订单里的商品明细、做退货退款操作这种设计直接就哑火了。正确的做法是拆成订单表和订单项表。订单表只存一次订单维度的信息订单号、用户ID、总金额、订单状态、收货地址快照、创建时间等。订单项表存的是这个订单里每一件商品的信息商品ID、商品名称快照、商品图片快照、购买单价、购买数量、小计金额。为什么要做快照因为商品信息是可变的。三个月后你回头看一个历史订单商品可能已经改价或者下架了如果订单项表只保存一个商品ID那么订单详情永远展示的是商品的最新信息这不符合业务逻辑。正确的做法是在生成订单的那一刻把商品名称、图片、单价原样复制到订单项表里让订单记录成为不可变的历史事实。订单状态字段我用int存储0表示已取消10表示待支付20表示已支付/待发货30表示已发货/待收货40表示已完成。用int而不是字符串的好处是状态流转可以用大小比较和枚举映射前端也能通过状态码直接映射对应的标签样式。3.4 逻辑删除与初始化数据商品表和分类表建议使用逻辑删除也就是加一个deleted字段查询时自动过滤。原因很简单商品是有关联数据的物理删除了商品历史订单项里可能还引用着它虽然订单项做了快照但管理端的统计查询会因此出现脏数据。逻辑删除的操作成本很低MyBatis-Plus直接支持TableLogic注解一行搞定。最后我要强调初始化数据的重要性。很多项目源码导入数据库之后前端页面空空荡荡不是因为代码有问题而是项目里没有带任何商品数据。一份好的源码必须在数据库脚本里预置至少二三十条商品信息、五六条分类信息、一张轮播图配置数据这样项目一跑起来就能直接展示效果答辩和演示的时候也不用手忙脚乱地现场录入。4. 后端接口落地细节从登录鉴权到库存扣减4.1 统一返回结构每个接口都长一个样在做具体业务之前先定义一个统一返回类。这个类不复杂但它的价值在于让前后端对接有了一个共同的契约。我常用的结构是code、message、data三个字段code为200表示成功其他值表示业务异常或系统异常。public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.code 200; result.message success; result.data data; return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.code code; result.message message; return result; } }有了统一返回结构配合全局异常处理器RestControllerAdvice后端代码里就不用到处写try-catch。业务异常直接抛一个自定义异常全局处理器统一捕获并封装成Result返回这样前端拿到的永远都是约定好的JSON格式。4.2 登录鉴权JWT无状态方案商城系统的登录状态管理我推荐使用JWT而不是传统的Session。原因是前后端分离之后后端接口可能是给Web端、也可能给移动端调用Session的跨域和共享问题会让人头疼而JWT本身就是无状态的后端只需要校验签名就知道用户是谁天然适合这种场景。JWT的落地分三块登录接口签发Token、拦截器校验Token、前端请求携带Token。登录成功后把用户ID和用户名放进Token的payload里设置一个合理的过期时间商城系统一般设为7天返回给前端。前端在axios请求拦截器里把Token放进Header的Authorization字段后端用一个拦截器或过滤器统一校验。拦截器里放过Swagger文档、登录接口、注册接口、商品浏览这些不需要鉴权的路径其余接口全部校验Token。校验通过后把用户ID放到ThreadLocal里业务代码直接通过UserContext.getUserId()获取当前登录用户不需要在每个Controller里传用户参数。4.3 密码存储加盐散列是底线前面提到password不能直接存MD5原因是彩虹表可以轻松反查出常见明文密码。实际项目中我一般用加盐MD5或者更稳妥的BCrypt。BCrypt的好处是每次哈希结果都不同而且还自带盐值Spring Security里有现成的BCryptPasswordEncoder可以直接用。如果你的项目没有引入Spring Security那也至少要在MD5之前拼接一个随机盐值然后把盐和密文一起存库校验时再取出来重新计算比对。4.4 商品列表分页MyBatis-Plus分页插件商品列表接口是商城访问频率最高的接口之一必须支持分页。用MyBatis-Plus的话只需要配置一个分页插件拦截器然后在Service里调用page方法传入当前页码和每页条数即可。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }商品列表接口要支持三个维度的筛选商品名称模糊搜索、分类ID精确筛选、价格区间筛选排序方式支持按销量、按价格、按上架时间。这些条件通过QueryWrapper动态拼装返回的Page对象里包含了总记录数、总页数、当前页数据列表前端拿到后直接渲染分页组件。4.5 购物车接口设计购物车在接口层面相对直接核心操作有六个添加购物车、修改数量、勾选/取消勾选、删除单项、获取购物车列表、清空已勾选商品。注意添加购物车时的幂等处理如果用户已经把某件商品加入过购物车再次添加时应该做数量累加而不是再插一条新记录。这个可以通过先查询user_id和product_id是否存在来实现。购物车列表返回时需要把购物车表和商品表做个关联查询带上商品名称、主图、现价、库存状态。如果商品已下架或者库存不足前端要在购物车列表中给出提示防止用户提交订单时才发现不能下单。4.6 下单接口的事务边界与库存扣减下单是商城系统中最考验代码功底的地方。一次下单操作涉及四件事校验商品信息是否上架、库存是否充足、计算订单总金额、扣减库存、生成订单和订单项。这四件事必须放在同一个数据库事务里任何一个环节失败前面所有的写入都要回滚。我的下单Service方法大致这样组织Transactional(rollbackFor Exception.class) public OrderVO createOrder(Long userId, ListCartItem checkedItems, Long addressId) { // 1. 校验商品与库存 for (CartItem item : checkedItems) { Product product productService.getById(item.getProductId()); if (product null || product.getStatus() 0) { throw new BizException(商品已下架: item.getProductName()); } if (product.getStock() item.getQuantity()) { throw new BizException(库存不足: product.getName()); } } // 2. 计算总金额以商品表实时价格为准不使用购物车里的价格 BigDecimal totalAmount BigDecimal.ZERO; for (CartItem item : checkedItems) { Product product productService.getById(item.getProductId()); totalAmount totalAmount.add(product.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))); } // 3. 扣减库存条件更新防止超卖 for (CartItem item : checkedItems) { boolean updated productService.deductStock(item.getProductId(), item.getQuantity()); if (!updated) { throw new BizException(库存不足: item.getProductName()); } } // 4. 生成订单主表数据与订单项明细 // ... }库存扣减的SQL要特别注意不能先查库存再在代码里判断够不够而要直接在SQL里用条件更新UPDATE product SET stock stock - #{quantity} WHERE id #{productId} AND stock #{quantity}这条SQL执行后返回的影响行数如果是0说明库存不足直接抛异常回滚事务。这种一次性条件更新是在不引入分布式锁的情况下最简单的防超卖手段。对单体商城项目来说这个方案已经足够健壮。4.7 跨域配置与全局异常前后端分离项目必定会遇到跨域问题。后端在配置类里统一放开跨域限制即可注意allowedOriginPatterns不要写成allowedOrigins(*)因为后者在携带凭证的请求下会失效。全局异常处理器需要覆盖几个数组场景参数校验异常MethodArgumentNotValidException、自定义业务异常BizException、兜底的Exception。每个异常类型返回对应的code前端response拦截器根据code做统一提示。5. Vue 前端的关键实现路由、状态、请求封装5.1 前端工程目录结构一个清晰的前端目录结构应该让人打开就能猜到每个文件大致负责什么。我的习惯是这样划分src/ api/ // 接口请求定义按模块拆分 assets/ // 静态资源 components/ // 公共组件 router/ // 路由配置 store/ // 全局状态 views/ home/ // 首页 product/ // 商品列表、详情 cart/ // 购物车 order/ // 订单确认、列表、详情 user/ // 登录、注册、个人中心、地址管理 admin/ // 后台管理模块 utils/ // 工具函数axios封装api目录按后端模块拆文件比如product.js、cart.js、order.js、user.js每个文件里的函数名与后端接口一一对应。这样做的好处是前端页面里不会出现一大片散落的axios调用接口变更时只需要集中修改api目录。5.2 axios请求封装axios封装是前端工程里最基础也最重要的一个公共模块。我把它分成三个关注点请求前自动附带Token、响应后统一处理业务码、网络异常统一提示。const service axios.create({ baseURL: /api, timeout: 10000 }); service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization token; } return config; }); service.interceptors.response.use( response { const res response.data; if (res.code 200) { return res.data; } // 业务异常统一提示 ElMessage.error(res.message || 请求失败); return Promise.reject(new Error(res.message)); }, error { ElMessage.error(网络异常请稍后重试); return Promise.reject(error); } );baseURL如果配置成/api后端就需要在CORS配置或网关层做对应映射。另一种更简单的做法是直接配完整后端地址比如http://localhost:8080然后通过Vite或Vue CLI的proxy做开发环境代理避免开发时反复被跨域困扰。5.3 路由规划与路由守卫前端路由分两块商城前台路由和管理后台路由。前台的路由包括首页、商品列表、商品详情、购物车、订单确认、订单列表、用户中心后台路由包括商品管理、分类管理、订单管理、用户管理。路由守卫的核心逻辑是校验登录态。我的做法是在全局前置守卫里判断目标路由meta上是否标记了requiresAuth如果需要登录但本地没有Token就跳转到登录页并带上redirect参数。后台路由额外加一层admin角色校验如果当前用户的角色不是admin直接跳到403页面。router.beforeEach((to, from, next) { const token localStorage.getItem(token); const user store.state.userInfo; if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }); } else if (to.path.startsWith(/admin) user?.role ! admin) { next(/403); } else { next(); } });5.4 全局状态管理的取舍商城项目里哪些数据需要放进全局状态我的经验是只放三类登录用户信息、购物车商品数量、订单确认时暂存的结算数据。其余页面级数据比如商品列表的筛选条件、详情页当前商品都放在组件内部或者通过路由参数传递没必要塞进store里增加维护成本。以Vue2项目为例Vuex的store结构大致是state里维护userInfo、token、cartCountmutations里做同步修改actions里调用登录接口、购物车接口并提交对应的mutation。购物车数量这个状态比较特殊它在很多页面的顶部导航栏都有展示所以必须全局管理每次购物车内容变更后后端接口返回最新的购物车总数前端提交mutation更新这个值。5.5 商品浏览到下单的数据流转这条链路值得完整走一遍因为它是整个前端最核心的流程。用户在商品列表页或首页点击某个商品卡片跳转到商品详情页详情页拿到路由里的商品ID后调用后端详情接口展示商品图片、价格、库存和详情描述。用户选择数量后点击加入购物车前端调购物车添加接口成功后更新全局的cartCount。进入购物车页面后用户勾选要结算的商品点击去结算跳转到订单确认页。订单确认页需要展示三个信息块收货地址列表可切换默认地址、商品清单来自已勾选的购物车项、金额汇总。用户点击提交订单前端带着勾选的购物车项和选中地址ID调用下单接口。下单成功后后端清空了对应的购物车项前端需要清掉本地临时状态、刷新cartCount然后带着后端返回的订单号跳转到支付页面或订单详情页。这个链条里的坑在于订单确认页的商品清单不能从购物车页面用路由传参带过去因为经过页面刷新之后参数可能丢失。正确做法是下单确认页再次拉取购物车列表并过滤出checked为true的项保证页面刷新后依然能正确展示待结算商品。6. 源码组织与文档交付让项目可复现、可答辩6.1 代码目录的后端规范后端代码用标准的Controller-Service-Mapper三层结构按业务模块分包。我见过太多把所有Controller塞在一个包里的项目几十个文件堆在一起打开IDE之后第一眼就毫无阅读欲望。按模块分包之后的效果是这样的com.shop common/ // 统一返回、全局异常、常量 config/ // 跨域配置、MyBatis-Plus配置、拦截器配置 controller/ // 按照 user/product/cart/order/address/admin 分包 service/ // 接口与实现 mapper/ // MyBatis-Plus的Mapper接口 entity/ // 与数据库表对应的实体类 dto/ // 前端入参封装 vo/ // 返回给前端的视图对象特别说一下VO和DTO的必要性。很多初学者喜欢直接拿Entity返回给前端数据库表里有密码字段的话登录接口把整个用户对象返回出去就是重大数据泄露。正确做法是定义UserVO只包含前端需要的字段将Entity转换为VO的过程放在Service层里完成。DTO则是用来承接前端入参的避免直接用Map或者多个散参接收这样参数校验也能写在DTO上。6.2 文档应该包含哪几样东西标题里提到的完整设计文档应当是一个四件套缺一不可。第一是项目说明文档也就是交付后别人拿到的第一份指引内容包含项目简介、技术栈清单、功能清单、项目结构说明、数据库说明第二是环境安装文档包含JDK、Maven、MySQL、Node.js、IDE的版本要求与安装步骤务必把每个版本号写死不要出现建议使用较新版本这种模糊表述第三是启动部署文档从导入数据库脚本、修改配置文件的数据库连接、启动后端、安装前端依赖、启动前端到浏览器访问的完整步骤第四是数据库设计文档用表格或简单的文本结构列出每张表的字段、类型、含义和表间关系。如果你想让项目更有专业度可以在这个基础上加一份接口文档。推荐使用Swagger方案SpringBoot集成springfox或springdoc之后启动项目访问/swagger-ui就能自动列出所有接口和参数比手写Word接口文档的维护成本低得多也更受工程界认可。6.3 项目README的写法README是整个源码交付的封面但多数项目的README不是写得太敷衍只有项目名称和一行简介就是太啰嗦把整个开发过程流水账一样贴上去。一份处理好坏的README应当达到这样的效果一个陌生开发者拿到源码后按照README的操作步骤在半小时内把项目跑起来。我的README模板包含以下八个小节项目简介、技术栈、功能特性、目录结构、环境要求、快速开始、项目截图或说明、常见问题。其中快速开始是核心必须精确到每一步。以数据库配置为例至少要写出在MySQL中执行sql/shop.sql脚本然后在application.yml中修改spring.datasource.username和password为你本地数据库的账号密码这样的具体指令。6.4 启动与环境适配的常见问题这个章节专门用来回答项目交付后被问得最多的几个本地跑不起来问题。第一个是端口占用。SpringBoot默认端口8080如果本机已经跑了其他服务启动会报Port already in use解决办法是在application.yml里修改server.port。第二个是MySQL版本不兼容。MySQL 8.0的驱动配置和5.7不一样驱动类要写成com.mysql.cj.jdbc.Driverurl里还要加serverTimezoneAsia/Shanghai否则会报时区错误。第三个是前端依赖安装失败。这个在前面环境配置部分已经提过切换npm镜像源用sass替代node-sass基本能解决90%的问题。第四个是前端调接口报404或跨域。开发环境下优先使用Vite的proxy或者Vue CLI的proxyTable做代理转发不要直接在前端代码里写死后端地址这样换环境部署时只需要改一处代理配置。6.5 打包与部署说明如果项目需要部署到服务器上给答辩老师或者客户看后端的SpringBoot项目用Maven打包成Jar文件执行mvn clean package然后使用java -jar命令启动即可。前端项目执行npm run build生成dist静态目录这个目录既可以交给Nginx托管也可以放到SpringBoot的static目录里由后端一起提供服务。我建议在文档里附一个最简部署方案前端构建后的文件拷贝到SpringBoot项目的src/main/resources/static目录下然后重新打包后端这样最终交付的产物就只有一个Jar文件。找一台服务器装上JDK和MySQL把这个Jar跑起来整个商城系统就完整运行了。这种方式对不具备前端服务器维护经验的使用者来说是最友好的交付形态。虽然这种做法纯前端架构师可能会觉得不够优雅但对小项目和毕业设计来说能用最短时间让系统跑起来比什么都重要。最后说一点个人经验做全栈项目真正拉开差距的从来不是代码量而是对数据流和状态流转的掌控力。购物车怎么被订单消费、订单状态怎么流转、库存怎么安全扣减把这几条主线彻底想清楚商城项目的主体就已经完成八成。剩下的事情就是按照一套稳定的目录结构和文档规范把工程填满、把交付物做完整。你在这个过程里建立的建模思维和交付意识才是这个项目能带给你的最大回报。