SpringBoot+Vue电商系统实战:从数据库设计到部署全解析

SpringBoot+Vue电商系统实战:从数据库设计到部署全解析 1. 项目整体设计与思路拆解“衣依”这个项目名在毕设和课程设计圈子里出现频率相当高。说实话第一次看到这个标题的时候我脑海里基本就能勾勒出它的全貌一套典型的B2C服装电商管理系统前端用Vue做SPA单页应用后端用SpringBoot暴露RESTful接口MySQL存业务数据MyBatis负责数据库操作。技术链路清晰业务模型经典非常适合作为Java方向的综合性练手项目。这类项目之所以被反复选择最核心的原因是它覆盖了一条完整的电商闭环用户从注册登录、浏览商品、加入购物车、生成订单到管理员在后台进行商品管理、订单处理、数据统计。也就是说一个项目同时锻炼了面向用户的C端设计能力和面向运营的B端管理能力。对准备找工作的同学来说简历上写这样一个项目面试官能问的东西非常多——从框架原理到数据库设计从权限认证到并发控制全是高频考点。从架构选型来看前后端分离在这个项目里是绝对的主流方案。以前很多老毕设用JSPServlet那套把页面和后端代码揉在一起开发和维护体验都很痛苦。而SpringBootVue这种组合后端只需要专注输出JSON数据前端通过axios异步请求渲染页面两边可以完全并行开发联调阶段再对接接口。这种模式也是目前企业级开发的事实标准做完这个项目你去公司接手真实业务系统时不会觉得陌生。再说说为什么选择MySQLMyBatis这套组合。MySQL不用多讲开源免费生态成熟大学课程里教的也都是它。MyBatis的定位是半自动ORM框架SQL由开发者自己编写控制力更强特别适合业务逻辑复杂、需要精细优化查询的场景。比如服装商品有多规格、多图片、多分类标签这些属性关联查询和动态SQL的使用频率很高MyBatis的动态SQL能力在这里能发挥得淋漓尽致。相比之下JPA那种全自动映射虽然在简单CRUD时省事但遇到复杂查询和性能调优时反而会让人有种“框架替你做了决定”的失控感。技术方案确定之后整个项目的重心就落在两块一是数据库表结构的设计是否合理二是前后端功能模块的边界划分是否清晰。这两块搞定剩下的就是按部就班地堆代码了。2. 技术选型解构为什么是这套组合拳2.1 SpringBoot低配置启动的利器选SpringBoot最大的理由就是它把“配置地狱”的问题解决了。传统SSM项目里光是一个SpringSpringMVC的XML配置就能写好几页各种bean定义、扫描路径、事务代理看得人头大。SpringBoot通过自动配置和约定大于配置的思想让一个Web项目从一个空的Maven工程到能跑起来返回JSON前后用不了五分钟。具体到“衣依”这个项目SpringBoot带来的好处是隐形的但实实在在的项目启动内置Tomcat打包直接用Maven插件打成可执行的JAR包java -jar就能跑起来部署到服务器上简单到不行。对于学生党来说这大大降低了环境搭建的上手门槛能把更多精力放在业务代码上。版本选择方面有一个坑要注意就是热词里反复出现的“springboot版本太高”。很多人跟我一样喜欢一开始就上最新版本结果发现SpringBoot 3.x要求JDK 17而很多学校的课程和大多数面试环境还在用JDK 8。一旦版本对不上启动直接报错连带各种依赖的兼容性问题全冒出来了。我的建议是老老实实用SpringBoot 2.7.x这是支持JDK 8的最后一个稳定大版本兼容性最好网上能找到的资料也最多。2.2 Vue渐进式框架的工程化之选前端选择Vue是因为它的学习曲线在三大框架里最平缓。如果你是第一次接触前端工程化Vue的模板语法几乎不需要额外学习成本上手就能写页面。配合Vue Router做前端路由Vuex或Pinia做状态管理axios做HTTP请求一套完整的前端工程骨架就搭起来了。开发阶段还有一个利器叫Vue CLI现在新项目基本都是用Vite了。通过脚手架创建项目后src目录下按组件划分目录结构每个页面拆成独立的.vue文件组件间通过props和events通信逻辑清晰了不止一个层级。组件化开发还有一个好处公共部分比如导航栏、商品卡片抽成全局组件后每次复用就一行代码的事。项目里我用到了Element UI作为UI组件库表格、表单、分页、弹窗这些管理后台高频组件都是现成的样式统一交互完备不用自己造轮子。Element UI对Vue 2的支持最完善如果你用的Vue 3就需要换Element Plus了这两者的API有差异别混着用。2.3 MyBatis灵活与可控的平衡点MyBatis在这套组合里扮演的是数据库操作的最终执行者。它的核心思路是把SQL语句写在XML文件里与Java代码完全解耦。这意味着SQL调优的空间完全握在你自己手里系统上线后如果发现哪个查询慢直接优化SQL语句就行不需要改业务代码。拿“衣依”项目里的商品查询举例首页的商品列表需要按分类筛选、按价格排序、按关键字模糊搜索还要分页。这个需求用一条动态SQL就能搞定select idselectProductByCondition resultTypecom.yiyi.entity.Product SELECT p.*, c.category_name FROM t_product p LEFT JOIN t_category c ON p.category_id c.id where if testcategoryId ! null AND p.category_id #{categoryId} /if if testkeyword ! null and keyword ! AND (p.product_name LIKE CONCAT(%, #{keyword}, %) OR p.description LIKE CONCAT(%, #{keyword}, %)) /if if testminPrice ! null AND p.price gt; #{minPrice} /if if testmaxPrice ! null AND p.price lt; #{maxPrice} /if /where ORDER BY p.sales_count DESC LIMIT #{offset}, #{pageSize} /select这段SQL的巧妙之处在于where标签会自动处理AND前缀问题当前面没有条件时它不会在WHERE子句前留下一个多余的AND。if标签则实现了条件动态拼接用户只传价格区间就只按价格过滤只传关键字就只做模糊搜索。这种灵活度是JPA那种全自动框架给不了的。使用MyBatis还需要提一个冷知识在使用if标签比较单个字符数字时teststatus 1这个写法可能不生效。因为MyBatis在OGNL表达式解析时会把1当作字符类型而不是字符串导致比较结果始终为false。正确写法是teststatus 1.toString()或者直接用teststatus 1。像这种细节你在做订单状态筛选时很容易踩到。3. 数据库设计服装销售平台的核心基座3.1 表结构规划思路一个电商系统能不能撑住业务数据库设计占了60%的功劳。我在设计“衣依”的表结构时遵循的原则是核心业务表单独建表关联需求用外键或冗余字段解决多对多关系拆中间表。最终的核心表是这样的用户表t_user用户ID、用户名、密码MD5加密存储、昵称、手机号、邮箱、头像、注册时间商品表t_product商品ID、商品名称、商品编号、一级分类ID、二级分类ID、品牌、价格、市场价、库存、销量、主图、轮播图、详情描述、上架状态、创建时间分类表t_category分类ID、分类名称、父分类ID、排序号购物车表t_cart购物车ID、用户ID、商品ID、数量、添加时间订单表t_order订单ID、订单编号、用户ID、订单总金额、实付金额、优惠金额、收货人姓名、收货人电话、收货人地址、订单状态、支付时间、发货时间、完成时间、创建时间订单明细表t_order_item明细ID、订单ID、商品ID、商品名称快照、商品图片快照、单价、购买数量、小计金额收货地址表t_address地址ID、用户ID、收货人、电话、省份、城市、区县、详细地址、是否默认地址管理员表t_admin管理员ID、用户名、密码、角色、最近登录时间3.2 两个容易被忽略的设计细节第一个是订单明细表为什么要冗余商品名称和图片的快照字段。如果直接通过商品ID去关联查询商品表那么商品下架或者改名后历史订单里的商品信息就会跟着变或直接查不到这在真实电商业务里是绝对不允许的。用户在“我的订单”里看到的永远应该是下单那一刻的商品快照。第二个是订单表里为什么要同时有“订单总金额”和“实付金额”。订单总金额是购物车中的商品原价合计实付金额是减去优惠后的最终价格。这套设计为后续接入满减、优惠券、积分抵扣留好了扩展空间。我现在这套项目只做了简单的满减逻辑后续要接入优惠券加一张优惠券表然后计算实付金额时联动处理就行表结构不需要动。商品表的设计还需要特别考虑服装行业的特点同一款衣服可能有S、M、L、XL等多个尺码和不同颜色这属于SKU最小库存单位的概念。完整的做法是拆一张商品SKU表存储每个具体规格的库存和价格SPU表存商品公共信息。“衣依”项目如果按最简方案可以在主表里直接加一个“规格描述”字段存JSON字符串但不推荐——因为这种设计查询起来很费劲。如果要做到真正好用建议加一个子表CREATE TABLE t_product_sku ( id INT PRIMARY KEY AUTO_INCREMENT, product_id INT NOT NULL, sku_name VARCHAR(255) COMMENT 规格名如M码/黑色, stock INT DEFAULT 0, price DECIMAL(10,2), UNIQUE KEY uk_product_sku (product_id, sku_name) );3.3 金额字段的类型选择这里特别强调一下金额字段的类型一定用DECIMAL不要用FLOAT或DOUBLE。浮点数在计算机里是二进制存储的0.10.2的结果都不是0.3像价格这种涉及钱的字段一旦出现精度丢失问题很严重。DECIMAL(10,2)能精确表示10位整数加2位小数的数字对于服装单价来说完全够用了。我在实际项目中还踩过order by和group by的坑这里顺便提一句如果在订单明细表里用到了子查询或者UNION外面的排序条件和内部字段很可能对不上MySQL会报很奇怪的错误。建议所有关键查询都先写子查询确认结果集没问题再加排序和分页逐层包裹。4. 后端核心模块实现要点4.1 系统功能模块划分后端整体按业务模块分包controller只负责参数校验和结果封装service层处理业务逻辑mapper层通过MyBatis接口操作数据库。包结构如下com.yiyi ├── controller // 接口层 ├── service // 业务层 │ └── impl // 业务实现 ├── mapper // MyBatis接口 ├── entity // 实体类 ├── dto // 数据传输对象 ├── vo // 视图对象 ├── config // 配置类拦截器、跨域处理 ├── utils // 工具类 └── common // 通用返回结果、异常处理模块划分有意识地遵循了“高内聚低耦合”的原则业务模块之间不互相调用对方的service而是通过接口传递数据。比如下单操作会同时操作订单表、订单明细表、购物车表和商品库存这个流程在service层的createOrder方法内通过Transactional事务注解统一控制任何一个环节失败所有数据变更全部回滚不会出现扣了库存但订单没生成的脏数据。4.2 用户认证与权限控制实现管理后台和C端用户的权限是两套体系。普通用户通过用户名密码登录后后端生成一个JWT Token返回给前端前端把它存在localStorage里后续每次请求都在请求头中带上。后端通过一个拦截器统一校验Token的有效性并把解析出的用户ID放入ThreadLocal供后续业务使用。拦截器的核心逻辑是这样的public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 if (request.getRequestURI().contains(/user/login)) { return true; } String token request.getHeader(Authorization); if (token null || !JwtUtil.verify(token)) { response.setStatus(401); return false; } Integer userId JwtUtil.getUserId(token); UserContext.set(userId); return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { UserContext.clear(); } }这里踩过的一个坑是拦截器无法读取POST请求的request body因为流只能读一次。所以登录接口我干脆不做拦截而是直接在Controller里接收参数校验。对于需要从body中解析用户信息的操作统一通过前端在请求头中传userId参数配合后端校验避免了二次读取流的麻烦。管理员的认证逻辑更简单粗暴登录成功后把管理员对象放Session后台请求通过过滤器校验Session中是否有对应属性。管理后台不开放注册接口管理员账号都是提前写入数据库的安全边界很清晰。4.3 购物车与订单的状态流转购物车模块的核心操作是加入购物车时判断“该商品是否已经在购物车中”。如果在直接执行数量累加不在则新增一条记录。为了保证这个判断在并发情况下不出问题我对(user_id, product_id)建了唯一索引然后利用INSERT ... ON DUPLICATE KEY UPDATE这种MySQL原生语法一行SQL就能搞定加购的逻辑不需要先查询再判断再插入。订单状态是我专门用一个枚举类管理的public enum OrderStatus { UNPAID(0, 待付款), PENDING_DELIVERY(1, 待发货), PENDING_RECEIPT(2, 待收货), COMPLETED(3, 已完成), CANCELLED(4, 已取消); }每次状态变更都通过orderService.updateStatus(orderId, targetStatus)方法处理方法内部会先判断当前状态是否允许流转到目标状态。比如“待发货”的订单不允许直接改成“已完成”必须经过“待收货”。这个校验逻辑虽然简单但是在真实项目中订单状态的合法流转是核心业务规则必须从源头控制好。4.4 文件上传与图片管理服装商品的图片是吸引用户下单的关键因素所以商品上传功能里图片处理这块我做得比较完整。前端通过Element UI的Upload组件选择图片后以multipart/form-data格式POST到后端的/api/upload接口。后端接收到MultipartFile后先校验文件类型和后缀名然后生成UUID作为新文件名按日期分目录存储到服务器本地最后把可访问的URL路径存到数据库。存储路径的生成策略是{yyyyMMdd}/{UUID}.jpg这样按日期归档方便后续做定时清理。上传的图片还需要做压缩处理手机拍摄的照片动辄几兆直接展示太浪费带宽。后端通过Java自带的ImageIO把图片压缩到宽度不超过800像素、质量系数0.8再输出压缩后通常一张图就100~200KB页面加载速度快多了。4.5 后台数据统计商品管理是“衣依”后台的核心模块除了增删改查还有两个功能值得实现一个是商品销量排行一个是月度销售统计。前者通过GROUP BY product_id对订单明细表的已支付订单做聚合按销量降序排列取前10条展示。后者需要按月统计销售金额利用MySQL的DATE_FORMAT(create_time, %Y-%m)对创建时间格式化再按这个格式化结果分组。这两块功能实现难度不高但是展示了SQL的聚合能力面试时挂在项目经验里很有说服力。前端用ECharts渲染成柱状图和折线图月度销售趋势一眼可见管理后台的专业感一下就出来了。5. 前端Vue实现与环境搭建实操5.1 从零搭建Vue开发环境前端开发环境的第一步是安装Node.js。这里要提醒一下Node.js版本不是越高越好。Vue 2项目在Node 18以上的环境安装依赖时经常会报OpenSSL错误这是因为新版本Node的OpenSSL升级了旧的webpack版本不兼容。解决办法有两个一是装Node 16长期支持版二是在package.json里加一句engines: {node: 16 17}或者用NODE_OPTIONS--openssl-legacy-provider启动。我自己的习惯是直接用nvm管理多个Node版本切换方便。环境准备好后用Vue CLI创建项目npm install -g vue/cli vue create yiyi-frontend cd yiyi-frontend npm install element-ui axios vue-router vuex npm run serve启动后默认监听8080端口。这里有个开发阶段必然要处理的跨域问题前端页面跑在8080后端接口跑在8081浏览器出于同源策略会拦截跨域请求。解决方法是利用Vue CLI自带的devServer代理在vue.config.js里配置module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true, pathRewrite: { ^/api: /api } } } } }这样前端请求/api/user/login时开发服务器会自动把请求转发到后端的8081端口浏览器的同源策略就被绕过去了生产环境还用不到这个配置因为前后端部署在同一Nginx下通过location /api { proxy_pass ...; }做了反向代理不存在跨域问题。5.2 前端核心页面与组件拆解前端页面按用户端和管理端拆分。用户端包括首页商品橱窗、商品列表页支持分类筛选和排序、商品详情页、购物车、订单结算页、支付模拟页、个人中心我的订单、收货地址管理。管理端包括登录页、数据看板、商品管理、分类管理、订单管理、用户管理、系统设置。组件复用的思路体现在几个公共组件上ProductCard.vue商品卡片在首页和商品列表页复用接收一个product对象渲染商品缩略图、价格和标题PaginationBar.vue分页组件封装了页码切换逻辑通过事件派发页码变化通知父组件刷新数据UploadImage.vue图片上传组件内部封装了Element UI的Upload和表单校验逻辑在商品管理和分类管理里面都用到了状态管理方面购物车的角标数量是一个全局状态用户往购物车添加商品后所有页面的购物车角标都要同步更新。这个场景用Vuex的state来维护就非常合适在addToCart的mutation里commit新的总数页头组件通过mapGetters获取并渲染。5.3 路由守卫与权限控制前端路由的权限控制通过Vue Router的导航守卫实现。每个需要登录才能访问的路由在meta字段里标记requiresAuth: true然后通过全局前置守卫判断router.beforeEach((to, from, next) { if (to.meta.requiresAuth) { const token localStorage.getItem(token); if (!token) { next({ path: /login, query: { redirect: to.fullPath } }); return; } } next(); });管理端路由单独放在一个子路由配置里统一的父级路由先校验管理员身份没有登录就重定向到管理后台登录页。这套设计把鉴权逻辑集中在一个地方后续新增页面只需要配置meta信息即可不需要在页面里写重复的判断代码。5.4 打包部署与布局异常排查开发调试没问题后执行npm run build会生成dist目录。这里踩过一个很典型的坑在热词里也出现了——“vue 打包后布局异常”。开发环境跑得好好的一打包发布样式全乱了或者首页白屏。这个问题的第一个原因是publicPath的配置。Vue CLI默认的publicPath是/如果你把打包产物部署在服务器的子路径下比如https://example.com/store/所有资源路径都会变成/js/app.js自然找不到文件。解决办法是在vue.config.js里设置publicPath: ./这样所有资源路径都会改成相对路径。第二个原因是前端路由用了history模式。history模式依赖服务端把所有路由重写到index.html如果Nginx没配置对应的try_files规则用户直接访问/product/123这类深层链接时服务器返回404。两个解决办法要么改用hash模式URL里带#要么在Nginx配置里加上location / { try_files $uri $uri/ /index.html; }第三种布局异常的根源是CSS的全局样式污染了组件样式。Element UI的样式是全局引入的如果你自己在全局样式中写了覆盖或者重置样式在开发环境由于热更新机制不一定触发问题打包后的静态样式文件加载顺序一变问题就暴露了。这个排查起来比较费时建议项目里统一用Scoped样式尽量避免全局样式。6. 环境搭建、部署发布与常见问题排查6.1 本地开发环境配置说明完整的开发环境我从零理一遍。JDK安装后要配好JAVA_HOME和PATH注意Java 8对应的是jdk1.8.0_xxx这个目录配置到bin的上一级。IDE我推荐用IntelliJ IDEA社区版免费够用了。Maven安装后需要在settings.xml里配置阿里云镜像仓库否则从中央仓库拉依赖的速度会让人崩溃下载一个上百MB的jar包等上十分钟也是很常见的。MySQL安装过程中会遇到两个高频问题一是MySQL 8的默认认证插件是caching_sha2_password老版本的连接驱动和工具连不上报Unable to load authentication plugin的错误解决办法是换用MySQL 8对应的JDBC驱动或者把用户认证插件改成mysql_native_password二是安装完成后忘记root密码这个可以通过skip-grant-tables模式重启实例再重置密码但操作要谨慎。6.2 IDEA导入项目与启动配置后端项目用IDEA打开的时候选择“File - Open”选中pom.xml所在目录IDEA会识别为Maven项目并自动导入依赖。首次导入需要等Maven下载所有依赖包这个过程网络好可能一两分钟网络不好可能要等十几分钟。导入完成后需要检查项目的Project SDK是否选择了正确的JDK版本在“Project Structure - Project”里设置。运行前需要在application.yml里确认数据库连接信息spring: datasource: url: jdbc:mysql://localhost:3306/yiyi_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 servlet: multipart: max-file-size: 10MB max-request-size: 50MBurl里serverTimezoneAsia/Shanghai这个参数一定要配否则连接MySQL时会报时区错误。我遇到过很多次这个问题就是启动的时候连接数据库报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized归根结底都是没配时区。6.3 前后端联调的经验之谈前后端联调阶段最容易出问题的不是接口逻辑而是接口约定不一致。我在项目里总结了一套比较实用的做法后端先定义好返回的统一JSON格式比如{code: 200, message: success, data: {...}}并把所有接口的请求参数、响应示例整理成接口文档用Apifox或Postman的文档功能前端严格按照文档联调双方对着文档对照能减少大量“我觉得你传的是这个类型”的误会。第三方的坑也要提前预防比如axios请求拦截器里统一加了token但登录接口此时还没有token需要放行或者做特殊处理。我之前遇到过登录接口因为全局拦截器要求token而反复返回401排查了半天才发现问题出在请求拦截器把所有请求都加了Authorization头而后端登录接口恰恰需要跳过认证。这个需要在前端axios拦截器里对登录接口单独判断或者后端登录接口在鉴权拦截器中提前放行。6.4 常见问题排查速查表我把自己和身边人做这个项目时最常遇到的问题整理成了一张速查表方便大家排查问题现象根本原因解决办法Maven依赖下载卡死中央仓库访问慢配置阿里云镜像SpringBoot启动失败提示端口占用8080/8081端口被占用换端口或查杀占用进程数据库连接报时区错误url里缺少serverTimezone参数加上Asia/ShanghaiSQL语句报错Unknown column实体类属性与数据库字段命名不一致开启MyBatis驼峰映射map-underscore-to-camel-case: trueMyBatisif比较status 1不生效OGNL表达式把1当作字符改为status 1外层单引号或.toString()前端npm run serve报OpenSSL错误Node版本过高与webpack冲突安装Node 16或使用nvm切换Vue打包后白屏publicPath是绝对路径且部署在子路径配置publicPath: ./Vue history模式刷新404服务端没有重写路由Nginx配置try_files $uri $uri/ /index.html上传图片后页面上显示不出来图片存储路径与访问路径不符统一用虚拟路径映射本地磁盘目录6.5 MyBatis缓存相关的两个注意点热词里提到了MyBatis缓存这个知识点面试爱问项目里也确实有用到。MyBatis的一级缓存是SqlSession级别的默认开启。在同一个SqlSession中执行两次相同的查询第二次会直接命中缓存返回不会再去查数据库。这个特性看起来是优化但在一次事务里如果你第一次查了数据中途更新了这条数据又去查它拿到的可能是旧数据。解决方案很简单在查询方法上配置flushCachetrue或者更新操作后手动调用sqlSession.clearCache()。二级缓存是namespace级别的可以跨SqlSession共享。但要注意二级缓存针对的是单表查询一旦涉及关联查询缓存的对象可能是多个表数据的组合表数据变更后缓存不会自动失效容易产生脏数据。我在“衣依”项目里只在商品分类这种几乎不会变的字典数据上开了二级缓存订单、库存这类高频更新的核心数据一律不开。7. 项目运行全流程演示7.1 初始化数据与启动后端拿到完整源码后第一步是执行项目中提供的sql/yiyi_db.sql脚本这个脚本里包含了建库建表语句和初始数据。用Navicat或者命令行执行都可以执行完后检查一下几张核心表的记录数确认数据导入成功。接着修改application.yml里的数据库账号密码直接运行YiyiApplication.java的main方法。看到日志输出Tomcat started on port(s): 8081就说明后端启动成功了。此时访问http://localhost:8081/api/product/list如果返回JSON数据说明数据库连接和接口都正常。7.2 启动前端并浏览完整流程前端项目在yiyi-frontend目录下依次执行npm install npm run serve浏览器打开http://localhost:8080在首页可以看到商品推荐列表。自己注册一个新用户登录后体验完整购物流程浏览商品 - 搜索“连衣裙”关键字 - 加入购物车 - 结算 - 填写收货地址 - 提交订单 - 模拟支付。支付是模拟的逻辑点击“确认支付”后订单状态从待付款变成待发货。管理后台通过http://localhost:8080/admin访问默认管理员账号admin/admin123具体以源码为准。在管理后台可以新增分类、添加商品、修改库存、把待发货的订单标记为已发货然后回到用户端刷新“我的订单”能看到订单状态同步变化。这一整套流程走下来一个电商项目的核心业务闭环就完整了。7.3 生产环境部署要点项目如果需要在云服务器上部署让人在线访问需要做的事也不复杂。后端项目先用Maven打包mvn clean package -DskipTests生成的target/yiyi-server.jar就是可执行的Fat JAR放到服务器上执行nohup java -jar yiyi-server.jar log.log 21 后台运行。前端项目执行npm run build后把dist目录上传到服务器配置Nginx做静态文件服务和API反向代理server { listen 80; server_name your-domain.com; root /var/www/yiyi; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /upload/ { alias /data/yiyi/upload/; } }配置时注意proxy_pass后面如果带了/api/路径转发时会有路径重写的问题前后端的接口路径要仔细核对。图片上传路径的映射也要保持一致否则商品图片会显示不出来。8. 个人经验与项目复盘收获整套项目从零开始做完我的感受不是“我终于写完了一个项目”而是终于把课堂上学到那些零散的知识串联成了体系。SpringBoot的核心IOC和AOP不再只是面试题而在拦截器、事务处理、日志记录里真正用到了Vue的响应式原理不再只是概念而是实实在在体现在购物车角标数量自动更新上数据库的事务隔离级别、索引优化也因为订单并发和商品搜索场景变得有血有肉。最后再分享一个我在答辩和面试时总结的实用技巧介绍这个项目时不要平铺直叙地背功能列表而是抓两三个你在实现过程中真正思考过的问题来展开。比如可以讲购物车并发的处理方案可以讲订单状态机的设计思路可以讲为什么订单明细要冗余商品快照。这些细节才是你和那些“只会照着源码跑起来”的人拉开差距的地方。如果后续你还有余力这个项目可以做的扩展方向其实很多接入支付宝沙箱做真实验支付、引入Redis缓存热门商品、用ElasticSearch做商品搜索、加一个秒杀模块测试高并发处理能力。每做一步你的技术能力都会上一个台阶。但不管怎么扩展最核心的那条业务链路——用户从看到商品到下单支付再到管理员发货——始终是这个系统扎根的地基把它彻底吃透比盲目堆功能重要得多。