SpringBoot + Vue 的免税商品优选购物商城,单看标题就知道这是一个典型的 Java Web 全栈毕业设计。它不像纯前端项目那样只写页面,也不像纯后端项目那样只写接口,而是把用户端、管理端、数据库、部署脚本全部串成一条线。我最近帮人梳理过这类项目,说实话源码量不算特别大,但信息密度并不低:一个商品模块、一个购物车、一个订单流程、一套后台管理,再加权限校验和税价展示,几乎把大学阶段学到的 Java 技术栈全部揉进去了。这篇文章的定位很直接:给那些准备拿同类商城做毕设、或者想彻底搞懂全套业务闭环的同学一份“能落地”的拆解,从数据库怎么建表到接口怎么设计,从前端页面怎么对接后端到部署时最容易翻车的位置,我都会按实际跑通项目的顺序来讲。1. 项目全貌拆解:它到底解决了什么问题1.1 从标题里挖出来的功能清单“免税商品优选购物商城平台”这个命名,每一段都是有含义的,不是随便拼出来的。“免税商品”决定了业务场景不是普通超市电商,而是带有税价展示和价格对比的跨境购物场景。体现在数据层面,商品表里通常需要有原价、免税价、税费、折扣等字段;体现在结算层面,订单金额需要能区分商品总额、优惠金额、实付金额,这就比普通的小卖部项目多了一个可讲价的点。“优选”这两个字意味着平台上要有“精选推荐”这类运营逻辑,技术上可以做成首页轮播图、热门推荐位、按销量排序、按点击量推荐等。你不需要真的实现一套复杂的推荐算法,但至少让首页不是一张静态图片堆出来的,而是能从数据库里动态查一批商品出来展示,面试时能说清楚推荐位的排序规则就够用了。“购物商城”是核心闭环,用户登录后浏览商品,把商品加入购物车,生成订单,完成支付,最后在个人中心看到订单状态。管理端则负责商品上架、分类维护、订单发货、Banner管理。这套闭环覆盖了增删改查、权限控制、状态流转、前后端联调、文件上传这些高频考点,所以它才这么受毕设选题欢迎。“平台”这个词说明项目通常不只是一个用户端,还会带一个后台管理端,也就是前端有两套界面,后端要区分普通用户和管理员角色。所有接口都要做登录拦截和权限校验,管理员接口不能裸奔在公网上。我拿到这类项目时,第一步不是看代码,而是先把功能列成一张模块清单,对照着检查它的完整性。一个能拿出去答辩的商城项目,至少要有下面这些模块:模块功能点角色首页Banner轮播、商品分类导航、热门商品推荐用户商品分类检索、关键词搜索、商品详情、分页列表用户购物车加入购物车、修改数量、删除商品、全选/单选、金额合计用户订单创建订单、订单列表、订单详情、取消订单、确认收货用户支付模拟支付页、支付回调、支付状态变更用户个人中心登录注册、个人信息、修改密码、地址管理用户后台管理商品管理、分类管理、订单管理、轮播图管理、用户管理管理员如果标题里的项目对应的源码缺少其中某一项,比如只有商品展示没有购物车,或者只有用户端没有管理端,那不仅答辩容易被追问到难堪,功能闭环也是不完整的。拿到项目后别急着跑,先对着这张表过一遍,缺什么补什么。1.2 为什么选 SpringBoot + Vue 这套组合如果是五六年前,Java Web 毕设的主流组合还是 JSP + Servlet 或者 SSM 框架加一堆 JSP 页面。现在再做那种单体 JSP 项目,不是说不可以,而是大多数人对“前后端分离”的掌握程度已经成了基本预期。SpringBoot + Vue 之所以霸榜,不是因为技术最新,而是因为它在“复杂度可控”和“技术栈完整”之间找到了一个很舒服的平衡点。后端用 SpringBoot,省掉了 Spring 那一堆 XML 配置,起步就是一个可以跑的 Web 项目。再配上 MyBatis-Plus,连单表的 CRUD SQL 都省了大半,写代码效率非常高。接口风格做成 RESTful,返回统一 JSON,前端拿数据直接渲染。数据库用 MySQL,事务、关联查询这些基本功都在里面练到了。前端用 Vue,不管是 Vue2 还是 Vue3,它的核心思路都是一样的:数据驱动视图。你定义一个变量,把它渲染到页面上,用户操作触发事件,事件里调用后端接口,返回的数据再更新变量。对毕设来说,Vue 的单文件组件和官方生态已经很成熟,文档也多,遇到问题基本都能搜到答案。有人会问,那用 Spring Cloud 微服务架构不是显得更高级吗?我的建议很明确:不要为了“显得高级”去上微服务。毕设的项目体量就那么大,一个商品模块一个订单模块,微服务拆出来只会给自己找麻烦,服务注册、配置中心、网关、分布式事务,哪一个都够喝一壶的。SpringBoot + Vue 是刚好能把一个完整的商城业务讲清楚,又不至于被架构本身拖死的选择。2. 环境搭建与数据库设计,决定项目上限的部分2.1 开发环境与版本选型拿到一个 SpringBoot + Vue 项目,第一件事不是看业务代码,而是把环境先对齐。很多新手项目跑不起来的第一个原因,就是 JDK、Maven、Node 版本哪个都对不上。先说后端。JDK 建议用 8 或者 11,这两个版本在 Java 生态里兼容性最好。如果你拿到的源码用了更高版本的语法特性,比如 var 或者 switch 表达式,那就要 JDK 11 以上。SpringBoot 版本也要看一眼,2.x 和 3.x 差别不小,主要是 javax 命名空间变成了 jakarta,如果源码是 3.x 而你本地用的是 JDK 8,那是编译不过去的,因为 SpringBoot 3 最低要求 JDK 17。Maven 的话,用 IDEA 自带的 Maven 或者单独安装 3.6 以上的版本都可以,重点是要把镜像仓库配置好,不然第一次拉依赖能等到让人怀疑人生。Node 版本要特别注意,Vue2 配 Node 14、16 问题不大,Vue3 配 Node 16 以上更稳。如果项目是用 Vite 构建的,Node 版本太老会直接报错。数据库这块,MySQL 5.7 和 8.0 都常见,我更建议用 8.0,因为字符集、时区、身份认证方式都更现代。需要注意的是 MySQL 8 的驱动包和连接 URL 都要带 cj 前缀,时区参数也要处理,不然会碰到 8 小时的时区差问题。开发工具方面,后端用 IDEA,前端用 VS Code,数据库用 Navicat 或者 DataGrip。没有强制要求,但这些工具最顺手,遇到问题也最容易搜到对应解决方案。2.2 数据库表结构与核心字段设计数据库设计是这个项目里最不该偷懒的部分。我见过不少同学的商城项目,商品表就五六个字段,订单表只有一个总金额字段,表面上功能跑得通,但只要面试官问一句“订单详情怎么查”,立刻就露馅了。一个标准的商城项目,核心表至少有这些:用户表(user)用户表字段不必过于复杂,但要有角色区分:用户ID、用户名、密码(加密后)、昵称、头像、手机号、邮箱、角色(0用户/1管理员)、状态、注册时间角色字段很关键,它决定了登录后的权限。管理员不是单独建一张表,而是用角色的方式去区分,这在 JWT 鉴权时非常方便。商品表(goods)商品表是信息量最大的表,至少要包含:商品ID、商品名称、商品副标题、分类ID、主图URL、轮播图URL、原价、免税价、库存、销量、点击量、是否上架、是否推荐、创建时间、更新时间这里我特别说一下价格字段。普通商城一个价格字段就完了,但免税商品场景下,我用的是“原价 + 免税价”双字段,前端商城详情页可以做一个对比展示,结算时按免税价计算。如果项目里还提到税费,那就再加一个税率字段,或者用“是否含税”来标记,这样“免税”这个概念就有数据支撑了,而不是只在页面上写两个字。分类表(category)分类表字段简单:分类ID、分类名称、父分类ID、排序、图标。这里用父分类ID是为了支持二级分类,比如“美妆”下面可以分“护肤”“彩妆”,如果项目不打算做二级分类,父分类ID 也可以留着,以后扩展时不用改表结构。购物车表(cart)购物车表不能只存商品ID,要考虑用户、商品、数量、选中状态这几个维度:购物车ID、用户ID、商品ID、数量、是否选中、加入时间看到“是否选中”这个字段,你可能觉得多余,但前端购物车页面的全选功能、单选功能、结算时只计算选中商品,全靠这个字段配合实现。购物车数据如果不加这个字段,前端就只能用临时变量管理选中状态,刷新页面就丢了。订单表(orders)订单表是核心业务表,需要记录快照信息:订单ID、订单编号、用户ID、商品总额、优惠金额、实付金额、收货人、手机号、收货地址、订单状态、创建时间、支付时间、发货时间为什么说快照信息?因为订单生成之后,商品可能改价、可能下架,如果订单里只存商品ID,那么后续查历史订单时会发现价格对不上。所以至少要把“当时的下单价”冗余到订单明细表里,这是业务开发里的常见习惯。订单明细表(order_item)一张订单对应多个商品,所以需要明细表:明细ID、订单ID、商品ID、商品名称(快照)、商品主图(快照)、下单单价、数量商品名称和主图在明细表里再存一份,这样即使商品后来改名、换图,历史订单仍然能看到下单时的信息。这是很普通但很容易被忽略的细节,写的时候多敲四五个字段,用的时候省一大堆麻烦。轮播图表(banner)轮播图表字段包括:BannerID、标题、图片URL、链接地址、排序、是否展示。这些表建好之后,一个完整商城的数据库骨架就出来了。我还建议把建表脚本里加上测试数据,具体原因下一段讲。2.3 SQL脚本里容易被忽略的初始化数据很多网上流传的项目源码,SQL脚本只建了表结构,一行数据都不带。你把它跑起来,前端页面空荡荡的,首页没商品,后台没管理员,连登录都进不去,第一印象就垮了。正确做法是 SQL脚本里除了建表,还要插入必要的基础数据:管理员账号:密码要存加密后的值,不能存明文几个测试用户:方便演示登录若干测试商品:覆盖不同分类、不同价格段,至少让首页“优选推荐”有东西可展示几条轮播图记录:让首页Banner区域看起来像样初始化数据这块有个非常容易踩的坑:密码加密。如果后端用的加密方式是 BCrypt,那你不能直接在 SQL 里写一个明文密码再指望登录时自动加密,因为 BCrypt 每次加密结果都不同。正确做法是先写个小测试类,把密码加密后的字符串打印出来,再复制到 SQL 脚本里。MD5 虽然简单,但毕设答辩时基本都会被问一句“密码怎么存储”,建议还是用 BCrypt 这类带盐的加密方案,答起来更有底气。3. 后端业务与接口实现:别把接口写得像“数据库透传”3.1 用户认证:从 Session 到 JWT 的选型逻辑前后端分离的项目里,用户登录状态怎么维持是个绕不开的问题。传统的 Session 方案需要服务器保存会话,前端通过 Cookie 携带 SessionID,但这样做会碰到跨域时 Cookie 携带不便的问题,对部署也不友好。现在主流的做法就是用 JWT,把用户信息加密后放到一个 Token 里,前端每次请求在请求头里带上 Token,后端校验通过就放行,校验不通过就返回 401。JWT 的结构是三段式的:Header、Payload、Signature。Header 里声明加密算法,Payload 里存用户ID、用户名、角色、过期时间这些信息,Signature 是对前两段的签名,防止 Token 被篡改。后端代码里要做的事情很简单:登录时生成 Token,拦截器里校验 Token。实际做毕设时,我建议把 JWT 的过期时间设成 2 小时或者 4 小时,太长不安全,太短影响使用体验。实现方案在 SpringBoot 里通常用一个拦截器或者过滤器统一处理,放行登录接口和商品浏览接口,拦截需要登录的接口,管理员接口还要额外检查角色。注意:JWT 不是存在服务器上的,所以它没法主动“失效”。用户登出这个操作,前端把 Token 丢掉就行,真正的安全性靠过期时间兜底。如果你做的项目要求更严格,就需要引入黑名单机制或者改用 Redis 保存会话状态,但毕设场景里 JWT + 前端清理 Token 已经够用了。3.2 统一响应结果与异常处理:前端拿到的不该是裸JSON很多后端新手写的接口,返回什么就是什么,查询成功就返回一个对象,失败了就返回 null,异常直接抛到前端,连错误码都没有。前端拿到一堆乱七八糟的响应格式,联调的时候只能到处写判断,苦不堪言。我的习惯是定义统一返回结构 Result ,包含三个字段:状态码、消息、数据体。成功状态码用 200,失败用自定义错误码,比如 500 表示服务器异常,401 表示未登录,403 表示没有权限。前端拿到响应后只看状态码,再做对应操作。配合统一返回结构,还要有统一异常处理。SpringBoot 用 @RestControllerAdvice 加 @ExceptionHandler 就能完成全局异常捕获,把业务异常、参数校验异常、系统异常分别处理。参数校验用 @Validated 注解,请求体里加 @NotBlank、@Email、@Min 这些校验注解,校验失败时由全局异常处理器统一返回错误信息,而不是让框架默认的报错页面出现在前端。这一步做完,后端接口的“边界感”就出来了,答辩时可以说自己考虑了健壮性。3.3 商品模块与检索接口的设计思路商品列表页是商城项目的门面,接口设计直接影响前端页面的复杂度。我建议商品列表接口至少支持这些参数:当前页码、每页条数、分类ID、关键字、排序方式。排序方式可以支持综合排序、价格从低到高、价格从高到低、销量排序。这里我用 MyBatis-Plus 作为 ORM 框架,它自带分页插件,配合 LambdaQueryWrapper 写查询条件非常简洁。比如按分类和关键字查询商品,用 LambdaQueryWrapper 的 eq 方法加分类条件,再用 like 方法加模糊搜索关键字,最后排序字段用 orderBy 方法动态拼接。比起手写 XML 里的动态 SQL,这种方式写起来快,出错率也低。“优选推荐”商品的实现也不复杂。我在商品表里加一个 is_recommend 字段,后台管理时勾选推荐,首页接口就查 is_recommend 为 true 的商品,再按销量或者点击量倒序取前几条。这样既不需要搞复杂的推荐算法,又能让首页内容动态变化,每次刷新可能看到不同推荐结果,业务逻辑上说得通,实现成本也低。商品详情的接口除了返回商品基本信息,还可以顺便返回同分类的热门商品,这样前端详情页底部就能渲染一个“相关推荐”区域,页面更饱满。同时也给商品点击数加一的更新操作,虽然简单,但至少商品是有“活跃度”概念的,而不是死数据。3.4 购物车与订单状态机的实现重点购物车模块的接口非常固定:查询购物车列表、加入购物车、修改数量、切换选中状态、删除购物车项。这里有一个隐藏的业务点,就是加入购物车时如果该商品已经在购物车里,应该做数量累加而不是新增一条记录,否则用户反复点击加入购物车,购物车里会出现好几条同商品记录,体验很差。订单模块是整个项目的核心。创建订单时,后端按这些步骤走:第一步,根据前端传过来的商品ID和数量,查出商品当前价格;第二步,把购物车里的选中商品整理成订单明细,计算出商品总额;第三步,生成订单主表,订单编号用时间戳加随机数生成,保证唯一性;第四步,保存订单明细;第五步,扣减商品库存;第六步,清理购物车中已下单的商品;第七步,返回订单号给前端。这一步里最容易被问到的就是事务。上述操作要么全部成功,要么全部失败,不能出现订单生成了但库存没扣,或者库存扣了订单没了的情况。解决办法就是在创建订单的方法上加 @Transactional 注解,让 Spring 帮我们管理事务边界。需要提醒的是,事务要放在 public 方法上才生效,同类内部通过 this 调用方法时事务是不起作用的,这是新手经常踩的坑。订单状态我习惯用这几个值:0 表示待支付,1 表示已支付,2 表示已发货,3 表示确认收货,4 表示已取消。状态流向要符合业务逻辑,待支付订单可以取消,已支付订单可以发货,已发货订单可以确认收货。在订单管理的列表查询里,用状态字段做筛选让管理员能快速看到待发货的订单,整个流程就清晰了。3.5 支付环节:用回调而不是前端“口头通知”毕设项目对接真实支付平台需要商户号、密钥等资质,对学生来说门槛较高,所以最合理的做法是做一个模拟支付流程。我的实现思路是:用户提交订单后,后端生成订单,订单状态是待支付,前端跳转到“收银台页面”,展示应付金额和订单编号。页面上放一个“确认支付”按钮,点击后调用后端一个支付接口,支付接口里模拟支付成功逻辑,更新订单状态为已支付。这里要强调一个设计原则:订单状态必须由后端修改,不能让前端在页面里“假装支付成功”然后直接改状态。就算没有真实支付平台,我们也要模拟出回调的语义。前端调用了支付接口,才是真的完成了支付动作。抄别人的项目时尤其要注意这个问题,有些简化版的代码里订单创建后直接就把状态置为已支付,这个逻辑在答辩时说不过去。支付完成后,后端返回最新的订单状态,前端跳转到订单详情页,展示支付成功。个人中心的订单列表里,用户可以对已支付订单发起“模拟发货”流程(角色不同,操作不同),管理员发货后状态变为已发货。这一串状态流转走通,业务闭环就成立了。4. 前端 Vue 页面开发:页面很多,但套路是固定的4.1 前端项目结构与路由设计前端项目拿到手,先看目录结构。规范一点的项目里,src 下面会有 views 存放页面组件、components 存放公共组件、router 存放路由配置、api 存放接口请求封装、store 存放全局状态、utils 存放工具函数。如果项目里没有 api 目录,所有接口请求都写在页面组件里,那说明项目结构比较乱,二开起来会非常痛苦。路由设计上,首页、商品列表、商品详情这三个页面是可以公开访问的,购物车、订单、个人中心需要登录后才能访问,后台管理页面需要管理员权限才能访问。Vue Router 提供了路由守卫,在 beforeEach 里判断用户的登录状态,没登录的话跳转到登录页并带上回跳路径,登录成功后自动回到之前的页面。这个细节很实用,用户体验好,也容易在答辩时讲。4.2 Axios 请求封装与 Token 携带前后端联调最关键的一步是封装 Axios。我会在 utils 目录建一个 request.js,创建一个 Axios 实例,设置基础请求地址、超时时间,然后用请求拦截器把 Token 放到请求头里。响应拦截器做的事情更多,根据后端返回的状态码做统一处理,200 就直接返回数据,401 就清除本地登录信息并跳转到登录页,500 就弹出错误提示。Token 在 localStorage 或者 Vuex/Pinia 里存一份。刷新页面后登录状态不会丢,因为路由守卫里会去 localStorage 读取 Token 来判断用户是否登录。4.3 商品与购物车页面的关键逻辑商品列表页和详情页相对简单,主要把接口返回的数据渲染到页面上,注意图片地址为空时要有默认占位图,价格格式化要保留两位小数,这些细节虽然小,但影响观感。购物车页面是前端业务比较重的一块,尤其是金额计算。购物车里每一项可以单独选择,也可以全选,页面底部要实时显示选中商品的合计金额和总件数。我的做法是在 Cart 页面维护一个本地数组,每一项记录商品信息和选中状态,然后用 Vue 的计算属性 computed 对选中项做遍历,累加数量和金额。这样用户勾选任何一项,下面的合计数字都会同步变化。结算时,把选中项的ID和数量组装成数组传给后端,创建订单接口。订单详情页展示订单状态、收货人信息、商品列表和金额明细。这时后端返回的订单金额就是最终金额,前端不需要再做计算,因为价格以后端计算为准,前端只负责展示,这也是前后端分离的一个原则:凡是金额相关的操作都要以后端为准,前端不能也不应该去“算总价”。4.4 后台管理端:商品维护与订单处理的实现管理端页面从布局上用左侧菜单加右侧内容区,路由用嵌套路由实现。商品管理页是一个表格,带分页和搜索,点击新增或者编辑时弹出弹窗或者跳转到表单页,表单里用 Upload 组件上传商品图片。上传功能基于后端的文件上传接口,前端把文件传给后端,后端保存到服务器磁盘,返回图片访问 URL,前端把这个 URL 拼进商品信息里一起提交。这里有个容易出问题的地方:本地开发时后端返回的图片地址可能是 localhost:8080 开头的绝对路径,但前端跑在 localhost:5173,如果路径写死就会跨域拿不到图。我的处理方式是后端保存图片后只返回相对路径,前端在渲染图片时用当前环境的基础地址拼接,这样本地和部署到服务器后路径都不会错。订单管理页对管理员来说主要做两件事:查看订单列表和处理发货。列表用表格展示订单编号、用户、金额、状态、时间,状态用标签样式区分颜色,未发货的订单给一个“发货”按钮,点击后调用后端接口修改状态并更新发货时间。5. 接口文档与 SQL 脚本交付,别让源码栽在这些细节上5.1 一份能跑的接口文档要写清楚什么拿到带接口文档的项目,先看文档质量。很多所谓的接口文档就是复制了几段 controller 代码,既不完整也不规范。一份合格的接口文档至少要对每个接口写清楚六项内容:请求地址、请求方式、是否需要登录、请求参数(名称、类型、必填、说明)、成功返回示例、错误码说明。对毕设项目来说,接口文档建议写成 Markdown 表格,目录按模块分好,用户模块、商品模块、购物车模块、订单模块、管理模块。前端开发时可以照着一个接口一个接口对接,省去反复问后端的麻烦。更重要的是,答辩时评委通常会翻一眼文档,一份整理过的接口文档能很快挂住印象分。举个例子,商品列表接口的文档可以这样写:参数类型必填说明pageint是当前页码pageSizeint是每页条数categoryIdint否分类ID,不传查全部keywordstring否搜索关键字sortstring否排序:default/priceAsc/priceDesc/sales返回内容里要写明每条商品记录包含哪些字段,特别是图片地址和价格字段的数据类型,前端才能正确渲染。5.2 SQL 脚本交付时的发布规范SQL脚本交付这件事,看起来简单,实际上特别容易出问题。我建议 SQL 脚本文件分成三层:第一层是建库语句,包括数据库名和默认字符集;第二层是建表语句,每个表带上 drop table if exists 前缀,保证可以重复执行;第三层是初始化数据,包括管理员、测试用户、分类、商品、轮播图。字符集方面,建库时明确指定 utf8mb4,排序规则用 utf8mb4_general_ci,不然中文可能出现乱码。表之间如果有外键关系,建表顺序要注意先主后从,虽然 MyBatis-Plus 项目里通常用逻辑关联而不是数据库外键,但建表顺序还是会影响到导入时的报错。导入 SQL 时还容易遇到一个问题:如果 SQL 文件里有特殊字符或者表情符号,比如商品描述里带了一个 emoji,而字符集不是 utf8mb4,导入时会直接报错。这些细节在项目上线部署、数据库迁移时都会被放大,提前处理好能省很多事。6. 部署上线与常见问题排查实录6.1 跨域问题:本地联调和服务器部署两种处理方式前后端分离项目最典型的问题就是跨域。本地开发时,前端跑在 5173 端口,后端跑在 8080 端口,端口不一样,浏览器就会拦截跨域请求。有两种常规解决办法。第一种是后端写一个配置类,实现 WebMvcConfigurer,重写 addCorsMappings 方法,允许指定来源跨域访问。这种方式写起来简单,适合没有前端构建工具的本地调试,或者后端接口要对外提供服务的场景。第二种是前端在开发服务器配置里做转发,Vite 项目在 vite.config.js 里设置 server.proxy,把 /api 开头的请求转发到后端地址。这样对浏览器来说,前端页面和后端接口是同源的,浏览器就不会产生跨域拦截。生产环境部署时,一般由 Nginx 统一接管。前端构建出的静态文件放到 Nginx 的 html 目录,后端在服务器上启动在某个端口,Nginx 配置里把 /api 路径的请求转发到后端端口,其他路径走静态文件。这套部署方式是当前前后端分离项目的标准姿势,熟悉一遍对以后工作也有帮助。6.2 前端路由刷新 404:最经典的部署坑如果你把前端项目部署到 Nginx,然后直接访问某个子路由页面,比如 /cart,刷新一下可能就出现 404。原因是 Vue Router 默认用 history 模式,URL 里没有 # 号,看起来像真实路径,但 Nginx 里并没有这个物理路径,所以就报了 404。解决办法是在 Nginx 配置的 location / 里加上 try_files,让它找不到对应文件时回退到 index.html。这样前端路由自己会匹配对应的页面组件,问题就解决了。这个知识点几乎是部署类问题的必考项,网上源码的部署文档里如果没写配置,你大概率会踩这一脚。6.3 数据库连接与端口占用问题跑通项目时常见的报错有几个。第一个是数据库连不上,报 Access denied for user,检查数据库密码和配置文件里的密码是否一致,检查 MySQL 服务是否已经启动。第二个是端口被占,8080 端口被其他程序占用,可以通过改后端配置文件端口或者找出占用进程来解决。第三个是时区问题,报错里出现 server timezone 字样,在 JDBC 连接串后面加上 serverTimezone=Asia/Shanghai 即可。还有一个特别容易忽略的问题就是数据库版本和驱动版本不匹配。如果 MySQL 是 8.0,而 pom.xml 里的驱动是 5.1 系列的旧版本,启动时也可能报通信链路异常。解决办法是把连接驱动版本换成和数据库对应的版本。6.4 免税商品场景的税价逻辑演示这个项目的业务背景是免税商城,那演示时就要让这块有存在感。我的做法很简单:商品详情页同时展示原价和免税价,免税价的字体更大更显眼,原价加上删除线放在旁边,形成直观对比。购物车结算页里列出“商品总额”和“优惠金额”,优惠金额就是原价总额减去免税价总额的差值,这个数据在创建订单的接口里已经算好并传给前端了。答辩时如果被问到“免税价是怎么算的”,回答思路可以这样组织:商品表里存有原价字段,免税价字段是在上架时由运营人员根据规则填写的,相当于业务规则前置,后端在下单时不关心价格是怎么来的,只关心最终生效价是哪个字段。这套逻辑虽然简单,但它把业务和系统解耦了,就算题库规则怎么变,订单系统都不需要跟着改。还有一点值得提:库存扣减的并发处理。秒杀这类场景我建议不要在这个项目里实现,但可以在论文里提一句“未来可以引入 Redis 缓存和分布式锁来处理高并发下的库存扣减”,这样既不会增加开发工作量,又能体现你的思考深度。我个人在实际操作中的体会是,这类项目真正拉开差距的往往不是功能多少,而是边界处理。你看很多源码,主流程都通,一提交订单就崩,一删数据就报错,一上传图片就找不到路径,全是细节问题。另一个体会是拿到任何源码,先补数据再跑起来,把测试数据做好准备,把演示路径想好,答辩时就能从容很多。最后再分享一个扩展方向:如果你时间充裕,可以在现有商城基础上加一个 Redis 缓存热门的接口,加一个消息队列处理订单通知,或者用定时任务自动取消超时未支付订单。这几个点任何一个做出来都比换个前端框架更有含金量,而且它们都是真实业务里会用到的东西,对你后续找工作面试也有实实在在的帮助。
SpringBoot + Vue 的免税商品优选购物商城,单看标题就知道这是一个典型的 Java Web 全栈毕业设计。它不像纯前端项目那样只写页面,也不像纯后端项目那样只写接口,而是把用户端、管理端、数据库、部署脚本全部串成一条线。我最近帮人梳理过这类项目,说实话源码量不算特别大,但信息密度并不低:一个商品模块、一个购物车、一个订单流程、一套后台管理,再加权限校验和税价展示,几乎把大学阶段学到的 Java 技术栈全部揉进去了。这篇文章的定位很直接:给那些准备拿同类商城做毕设、或者想彻底搞懂全套业务闭环的同学一份“能落地”的拆解,从数据库怎么建表到接口怎么设计,从前端页面怎么对接后端到部署时最容易翻车的位置,我都会按实际跑通项目的顺序来讲。1. 项目全貌拆解:它到底解决了什么问题1.1 从标题里挖出来的功能清单“免税商品优选购物商城平台”这个命名,每一段都是有含义的,不是随便拼出来的。“免税商品”决定了业务场景不是普通超市电商,而是带有税价展示和价格对比的跨境购物场景。体现在数据层面,商品表里通常需要有原价、免税价、税费、折扣等字段;体现在结算层面,订单金额需要能区分商品总额、优惠金额、实付金额,这就比普通的小卖部项目多了一个可讲价的点。“优选”这两个字意味着平台上要有“精选推荐”这类运营逻辑,技术上可以做成首页轮播图、热门推荐位、按销量排序、按点击量推荐等。你不需要真的实现一套复杂的推荐算法,但至少让首页不是一张静态图片堆出来的,而是能从数据库里动态查一批商品出来展示,面试时能说清楚推荐位的排序规则就够用了。“购物商城”是核心闭环,用户登录后浏览商品,把商品加入购物车,生成订单,完成支付,最后在个人中心看到订单状态。管理端则负责商品上架、分类维护、订单发货、Banner管理。这套闭环覆盖了增删改查、权限控制、状态流转、前后端联调、文件上传这些高频考点,所以它才这么受毕设选题欢迎。“平台”这个词说明项目通常不只是一个用户端,还会带一个后台管理端,也就是前端有两套界面,后端要区分普通用户和管理员角色。所有接口都要做登录拦截和权限校验,管理员接口不能裸奔在公网上。我拿到这类项目时,第一步不是看代码,而是先把功能列成一张模块清单,对照着检查它的完整性。一个能拿出去答辩的商城项目,至少要有下面这些模块:模块功能点角色首页Banner轮播、商品分类导航、热门商品推荐用户商品分类检索、关键词搜索、商品详情、分页列表用户购物车加入购物车、修改数量、删除商品、全选/单选、金额合计用户订单创建订单、订单列表、订单详情、取消订单、确认收货用户支付模拟支付页、支付回调、支付状态变更用户个人中心登录注册、个人信息、修改密码、地址管理用户后台管理商品管理、分类管理、订单管理、轮播图管理、用户管理管理员如果标题里的项目对应的源码缺少其中某一项,比如只有商品展示没有购物车,或者只有用户端没有管理端,那不仅答辩容易被追问到难堪,功能闭环也是不完整的。拿到项目后别急着跑,先对着这张表过一遍,缺什么补什么。1.2 为什么选 SpringBoot + Vue 这套组合如果是五六年前,Java Web 毕设的主流组合还是 JSP + Servlet 或者 SSM 框架加一堆 JSP 页面。现在再做那种单体 JSP 项目,不是说不可以,而是大多数人对“前后端分离”的掌握程度已经成了基本预期。SpringBoot + Vue 之所以霸榜,不是因为技术最新,而是因为它在“复杂度可控”和“技术栈完整”之间找到了一个很舒服的平衡点。后端用 SpringBoot,省掉了 Spring 那一堆 XML 配置,起步就是一个可以跑的 Web 项目。再配上 MyBatis-Plus,连单表的 CRUD SQL 都省了大半,写代码效率非常高。接口风格做成 RESTful,返回统一 JSON,前端拿数据直接渲染。数据库用 MySQL,事务、关联查询这些基本功都在里面练到了。前端用 Vue,不管是 Vue2 还是 Vue3,它的核心思路都是一样的:数据驱动视图。你定义一个变量,把它渲染到页面上,用户操作触发事件,事件里调用后端接口,返回的数据再更新变量。对毕设来说,Vue 的单文件组件和官方生态已经很成熟,文档也多,遇到问题基本都能搜到答案。有人会问,那用 Spring Cloud 微服务架构不是显得更高级吗?我的建议很明确:不要为了“显得高级”去上微服务。毕设的项目体量就那么大,一个商品模块一个订单模块,微服务拆出来只会给自己找麻烦,服务注册、配置中心、网关、分布式事务,哪一个都够喝一壶的。SpringBoot + Vue 是刚好能把一个完整的商城业务讲清楚,又不至于被架构本身拖死的选择。2. 环境搭建与数据库设计,决定项目上限的部分2.1 开发环境与版本选型拿到一个 SpringBoot + Vue 项目,第一件事不是看业务代码,而是把环境先对齐。很多新手项目跑不起来的第一个原因,就是 JDK、Maven、Node 版本哪个都对不上。先说后端。JDK 建议用 8 或者 11,这两个版本在 Java 生态里兼容性最好。如果你拿到的源码用了更高版本的语法特性,比如 var 或者 switch 表达式,那就要 JDK 11 以上。SpringBoot 版本也要看一眼,2.x 和 3.x 差别不小,主要是 javax 命名空间变成了 jakarta,如果源码是 3.x 而你本地用的是 JDK 8,那是编译不过去的,因为 SpringBoot 3 最低要求 JDK 17。Maven 的话,用 IDEA 自带的 Maven 或者单独安装 3.6 以上的版本都可以,重点是要把镜像仓库配置好,不然第一次拉依赖能等到让人怀疑人生。Node 版本要特别注意,Vue2 配 Node 14、16 问题不大,Vue3 配 Node 16 以上更稳。如果项目是用 Vite 构建的,Node 版本太老会直接报错。数据库这块,MySQL 5.7 和 8.0 都常见,我更建议用 8.0,因为字符集、时区、身份认证方式都更现代。需要注意的是 MySQL 8 的驱动包和连接 URL 都要带 cj 前缀,时区参数也要处理,不然会碰到 8 小时的时区差问题。开发工具方面,后端用 IDEA,前端用 VS Code,数据库用 Navicat 或者 DataGrip。没有强制要求,但这些工具最顺手,遇到问题也最容易搜到对应解决方案。2.2 数据库表结构与核心字段设计数据库设计是这个项目里最不该偷懒的部分。我见过不少同学的商城项目,商品表就五六个字段,订单表只有一个总金额字段,表面上功能跑得通,但只要面试官问一句“订单详情怎么查”,立刻就露馅了。一个标准的商城项目,核心表至少有这些:用户表(user)用户表字段不必过于复杂,但要有角色区分:用户ID、用户名、密码(加密后)、昵称、头像、手机号、邮箱、角色(0用户/1管理员)、状态、注册时间角色字段很关键,它决定了登录后的权限。管理员不是单独建一张表,而是用角色的方式去区分,这在 JWT 鉴权时非常方便。商品表(goods)商品表是信息量最大的表,至少要包含:商品ID、商品名称、商品副标题、分类ID、主图URL、轮播图URL、原价、免税价、库存、销量、点击量、是否上架、是否推荐、创建时间、更新时间这里我特别说一下价格字段。普通商城一个价格字段就完了,但免税商品场景下,我用的是“原价 + 免税价”双字段,前端商城详情页可以做一个对比展示,结算时按免税价计算。如果项目里还提到税费,那就再加一个税率字段,或者用“是否含税”来标记,这样“免税”这个概念就有数据支撑了,而不是只在页面上写两个字。分类表(category)分类表字段简单:分类ID、分类名称、父分类ID、排序、图标。这里用父分类ID是为了支持二级分类,比如“美妆”下面可以分“护肤”“彩妆”,如果项目不打算做二级分类,父分类ID 也可以留着,以后扩展时不用改表结构。购物车表(cart)购物车表不能只存商品ID,要考虑用户、商品、数量、选中状态这几个维度:购物车ID、用户ID、商品ID、数量、是否选中、加入时间看到“是否选中”这个字段,你可能觉得多余,但前端购物车页面的全选功能、单选功能、结算时只计算选中商品,全靠这个字段配合实现。购物车数据如果不加这个字段,前端就只能用临时变量管理选中状态,刷新页面就丢了。订单表(orders)订单表是核心业务表,需要记录快照信息:订单ID、订单编号、用户ID、商品总额、优惠金额、实付金额、收货人、手机号、收货地址、订单状态、创建时间、支付时间、发货时间为什么说快照信息?因为订单生成之后,商品可能改价、可能下架,如果订单里只存商品ID,那么后续查历史订单时会发现价格对不上。所以至少要把“当时的下单价”冗余到订单明细表里,这是业务开发里的常见习惯。订单明细表(order_item)一张订单对应多个商品,所以需要明细表:明细ID、订单ID、商品ID、商品名称(快照)、商品主图(快照)、下单单价、数量商品名称和主图在明细表里再存一份,这样即使商品后来改名、换图,历史订单仍然能看到下单时的信息。这是很普通但很容易被忽略的细节,写的时候多敲四五个字段,用的时候省一大堆麻烦。轮播图表(banner)轮播图表字段包括:BannerID、标题、图片URL、链接地址、排序、是否展示。这些表建好之后,一个完整商城的数据库骨架就出来了。我还建议把建表脚本里加上测试数据,具体原因下一段讲。2.3 SQL脚本里容易被忽略的初始化数据很多网上流传的项目源码,SQL脚本只建了表结构,一行数据都不带。你把它跑起来,前端页面空荡荡的,首页没商品,后台没管理员,连登录都进不去,第一印象就垮了。正确做法是 SQL脚本里除了建表,还要插入必要的基础数据:管理员账号:密码要存加密后的值,不能存明文几个测试用户:方便演示登录若干测试商品:覆盖不同分类、不同价格段,至少让首页“优选推荐”有东西可展示几条轮播图记录:让首页Banner区域看起来像样初始化数据这块有个非常容易踩的坑:密码加密。如果后端用的加密方式是 BCrypt,那你不能直接在 SQL 里写一个明文密码再指望登录时自动加密,因为 BCrypt 每次加密结果都不同。正确做法是先写个小测试类,把密码加密后的字符串打印出来,再复制到 SQL 脚本里。MD5 虽然简单,但毕设答辩时基本都会被问一句“密码怎么存储”,建议还是用 BCrypt 这类带盐的加密方案,答起来更有底气。3. 后端业务与接口实现:别把接口写得像“数据库透传”3.1 用户认证:从 Session 到 JWT 的选型逻辑前后端分离的项目里,用户登录状态怎么维持是个绕不开的问题。传统的 Session 方案需要服务器保存会话,前端通过 Cookie 携带 SessionID,但这样做会碰到跨域时 Cookie 携带不便的问题,对部署也不友好。现在主流的做法就是用 JWT,把用户信息加密后放到一个 Token 里,前端每次请求在请求头里带上 Token,后端校验通过就放行,校验不通过就返回 401。JWT 的结构是三段式的:Header、Payload、Signature。Header 里声明加密算法,Payload 里存用户ID、用户名、角色、过期时间这些信息,Signature 是对前两段的签名,防止 Token 被篡改。后端代码里要做的事情很简单:登录时生成 Token,拦截器里校验 Token。实际做毕设时,我建议把 JWT 的过期时间设成 2 小时或者 4 小时,太长不安全,太短影响使用体验。实现方案在 SpringBoot 里通常用一个拦截器或者过滤器统一处理,放行登录接口和商品浏览接口,拦截需要登录的接口,管理员接口还要额外检查角色。注意:JWT 不是存在服务器上的,所以它没法主动“失效”。用户登出这个操作,前端把 Token 丢掉就行,真正的安全性靠过期时间兜底。如果你做的项目要求更严格,就需要引入黑名单机制或者改用 Redis 保存会话状态,但毕设场景里 JWT + 前端清理 Token 已经够用了。3.2 统一响应结果与异常处理:前端拿到的不该是裸JSON很多后端新手写的接口,返回什么就是什么,查询成功就返回一个对象,失败了就返回 null,异常直接抛到前端,连错误码都没有。前端拿到一堆乱七八糟的响应格式,联调的时候只能到处写判断,苦不堪言。我的习惯是定义统一返回结构 Result ,包含三个字段:状态码、消息、数据体。成功状态码用 200,失败用自定义错误码,比如 500 表示服务器异常,401 表示未登录,403 表示没有权限。前端拿到响应后只看状态码,再做对应操作。配合统一返回结构,还要有统一异常处理。SpringBoot 用 @RestControllerAdvice 加 @ExceptionHandler 就能完成全局异常捕获,把业务异常、参数校验异常、系统异常分别处理。参数校验用 @Validated 注解,请求体里加 @NotBlank、@Email、@Min 这些校验注解,校验失败时由全局异常处理器统一返回错误信息,而不是让框架默认的报错页面出现在前端。这一步做完,后端接口的“边界感”就出来了,答辩时可以说自己考虑了健壮性。3.3 商品模块与检索接口的设计思路商品列表页是商城项目的门面,接口设计直接影响前端页面的复杂度。我建议商品列表接口至少支持这些参数:当前页码、每页条数、分类ID、关键字、排序方式。排序方式可以支持综合排序、价格从低到高、价格从高到低、销量排序。这里我用 MyBatis-Plus 作为 ORM 框架,它自带分页插件,配合 LambdaQueryWrapper 写查询条件非常简洁。比如按分类和关键字查询商品,用 LambdaQueryWrapper 的 eq 方法加分类条件,再用 like 方法加模糊搜索关键字,最后排序字段用 orderBy 方法动态拼接。比起手写 XML 里的动态 SQL,这种方式写起来快,出错率也低。“优选推荐”商品的实现也不复杂。我在商品表里加一个 is_recommend 字段,后台管理时勾选推荐,首页接口就查 is_recommend 为 true 的商品,再按销量或者点击量倒序取前几条。这样既不需要搞复杂的推荐算法,又能让首页内容动态变化,每次刷新可能看到不同推荐结果,业务逻辑上说得通,实现成本也低。商品详情的接口除了返回商品基本信息,还可以顺便返回同分类的热门商品,这样前端详情页底部就能渲染一个“相关推荐”区域,页面更饱满。同时也给商品点击数加一的更新操作,虽然简单,但至少商品是有“活跃度”概念的,而不是死数据。3.4 购物车与订单状态机的实现重点购物车模块的接口非常固定:查询购物车列表、加入购物车、修改数量、切换选中状态、删除购物车项。这里有一个隐藏的业务点,就是加入购物车时如果该商品已经在购物车里,应该做数量累加而不是新增一条记录,否则用户反复点击加入购物车,购物车里会出现好几条同商品记录,体验很差。订单模块是整个项目的核心。创建订单时,后端按这些步骤走:第一步,根据前端传过来的商品ID和数量,查出商品当前价格;第二步,把购物车里的选中商品整理成订单明细,计算出商品总额;第三步,生成订单主表,订单编号用时间戳加随机数生成,保证唯一性;第四步,保存订单明细;第五步,扣减商品库存;第六步,清理购物车中已下单的商品;第七步,返回订单号给前端。这一步里最容易被问到的就是事务。上述操作要么全部成功,要么全部失败,不能出现订单生成了但库存没扣,或者库存扣了订单没了的情况。解决办法就是在创建订单的方法上加 @Transactional 注解,让 Spring 帮我们管理事务边界。需要提醒的是,事务要放在 public 方法上才生效,同类内部通过 this 调用方法时事务是不起作用的,这是新手经常踩的坑。订单状态我习惯用这几个值:0 表示待支付,1 表示已支付,2 表示已发货,3 表示确认收货,4 表示已取消。状态流向要符合业务逻辑,待支付订单可以取消,已支付订单可以发货,已发货订单可以确认收货。在订单管理的列表查询里,用状态字段做筛选让管理员能快速看到待发货的订单,整个流程就清晰了。3.5 支付环节:用回调而不是前端“口头通知”毕设项目对接真实支付平台需要商户号、密钥等资质,对学生来说门槛较高,所以最合理的做法是做一个模拟支付流程。我的实现思路是:用户提交订单后,后端生成订单,订单状态是待支付,前端跳转到“收银台页面”,展示应付金额和订单编号。页面上放一个“确认支付”按钮,点击后调用后端一个支付接口,支付接口里模拟支付成功逻辑,更新订单状态为已支付。这里要强调一个设计原则:订单状态必须由后端修改,不能让前端在页面里“假装支付成功”然后直接改状态。就算没有真实支付平台,我们也要模拟出回调的语义。前端调用了支付接口,才是真的完成了支付动作。抄别人的项目时尤其要注意这个问题,有些简化版的代码里订单创建后直接就把状态置为已支付,这个逻辑在答辩时说不过去。支付完成后,后端返回最新的订单状态,前端跳转到订单详情页,展示支付成功。个人中心的订单列表里,用户可以对已支付订单发起“模拟发货”流程(角色不同,操作不同),管理员发货后状态变为已发货。这一串状态流转走通,业务闭环就成立了。4. 前端 Vue 页面开发:页面很多,但套路是固定的4.1 前端项目结构与路由设计前端项目拿到手,先看目录结构。规范一点的项目里,src 下面会有 views 存放页面组件、components 存放公共组件、router 存放路由配置、api 存放接口请求封装、store 存放全局状态、utils 存放工具函数。如果项目里没有 api 目录,所有接口请求都写在页面组件里,那说明项目结构比较乱,二开起来会非常痛苦。路由设计上,首页、商品列表、商品详情这三个页面是可以公开访问的,购物车、订单、个人中心需要登录后才能访问,后台管理页面需要管理员权限才能访问。Vue Router 提供了路由守卫,在 beforeEach 里判断用户的登录状态,没登录的话跳转到登录页并带上回跳路径,登录成功后自动回到之前的页面。这个细节很实用,用户体验好,也容易在答辩时讲。4.2 Axios 请求封装与 Token 携带前后端联调最关键的一步是封装 Axios。我会在 utils 目录建一个 request.js,创建一个 Axios 实例,设置基础请求地址、超时时间,然后用请求拦截器把 Token 放到请求头里。响应拦截器做的事情更多,根据后端返回的状态码做统一处理,200 就直接返回数据,401 就清除本地登录信息并跳转到登录页,500 就弹出错误提示。Token 在 localStorage 或者 Vuex/Pinia 里存一份。刷新页面后登录状态不会丢,因为路由守卫里会去 localStorage 读取 Token 来判断用户是否登录。4.3 商品与购物车页面的关键逻辑商品列表页和详情页相对简单,主要把接口返回的数据渲染到页面上,注意图片地址为空时要有默认占位图,价格格式化要保留两位小数,这些细节虽然小,但影响观感。购物车页面是前端业务比较重的一块,尤其是金额计算。购物车里每一项可以单独选择,也可以全选,页面底部要实时显示选中商品的合计金额和总件数。我的做法是在 Cart 页面维护一个本地数组,每一项记录商品信息和选中状态,然后用 Vue 的计算属性 computed 对选中项做遍历,累加数量和金额。这样用户勾选任何一项,下面的合计数字都会同步变化。结算时,把选中项的ID和数量组装成数组传给后端,创建订单接口。订单详情页展示订单状态、收货人信息、商品列表和金额明细。这时后端返回的订单金额就是最终金额,前端不需要再做计算,因为价格以后端计算为准,前端只负责展示,这也是前后端分离的一个原则:凡是金额相关的操作都要以后端为准,前端不能也不应该去“算总价”。4.4 后台管理端:商品维护与订单处理的实现管理端页面从布局上用左侧菜单加右侧内容区,路由用嵌套路由实现。商品管理页是一个表格,带分页和搜索,点击新增或者编辑时弹出弹窗或者跳转到表单页,表单里用 Upload 组件上传商品图片。上传功能基于后端的文件上传接口,前端把文件传给后端,后端保存到服务器磁盘,返回图片访问 URL,前端把这个 URL 拼进商品信息里一起提交。这里有个容易出问题的地方:本地开发时后端返回的图片地址可能是 localhost:8080 开头的绝对路径,但前端跑在 localhost:5173,如果路径写死就会跨域拿不到图。我的处理方式是后端保存图片后只返回相对路径,前端在渲染图片时用当前环境的基础地址拼接,这样本地和部署到服务器后路径都不会错。订单管理页对管理员来说主要做两件事:查看订单列表和处理发货。列表用表格展示订单编号、用户、金额、状态、时间,状态用标签样式区分颜色,未发货的订单给一个“发货”按钮,点击后调用后端接口修改状态并更新发货时间。5. 接口文档与 SQL 脚本交付,别让源码栽在这些细节上5.1 一份能跑的接口文档要写清楚什么拿到带接口文档的项目,先看文档质量。很多所谓的接口文档就是复制了几段 controller 代码,既不完整也不规范。一份合格的接口文档至少要对每个接口写清楚六项内容:请求地址、请求方式、是否需要登录、请求参数(名称、类型、必填、说明)、成功返回示例、错误码说明。对毕设项目来说,接口文档建议写成 Markdown 表格,目录按模块分好,用户模块、商品模块、购物车模块、订单模块、管理模块。前端开发时可以照着一个接口一个接口对接,省去反复问后端的麻烦。更重要的是,答辩时评委通常会翻一眼文档,一份整理过的接口文档能很快挂住印象分。举个例子,商品列表接口的文档可以这样写:参数类型必填说明pageint是当前页码pageSizeint是每页条数categoryIdint否分类ID,不传查全部keywordstring否搜索关键字sortstring否排序:default/priceAsc/priceDesc/sales返回内容里要写明每条商品记录包含哪些字段,特别是图片地址和价格字段的数据类型,前端才能正确渲染。5.2 SQL 脚本交付时的发布规范SQL脚本交付这件事,看起来简单,实际上特别容易出问题。我建议 SQL 脚本文件分成三层:第一层是建库语句,包括数据库名和默认字符集;第二层是建表语句,每个表带上 drop table if exists 前缀,保证可以重复执行;第三层是初始化数据,包括管理员、测试用户、分类、商品、轮播图。字符集方面,建库时明确指定 utf8mb4,排序规则用 utf8mb4_general_ci,不然中文可能出现乱码。表之间如果有外键关系,建表顺序要注意先主后从,虽然 MyBatis-Plus 项目里通常用逻辑关联而不是数据库外键,但建表顺序还是会影响到导入时的报错。导入 SQL 时还容易遇到一个问题:如果 SQL 文件里有特殊字符或者表情符号,比如商品描述里带了一个 emoji,而字符集不是 utf8mb4,导入时会直接报错。这些细节在项目上线部署、数据库迁移时都会被放大,提前处理好能省很多事。6. 部署上线与常见问题排查实录6.1 跨域问题:本地联调和服务器部署两种处理方式前后端分离项目最典型的问题就是跨域。本地开发时,前端跑在 5173 端口,后端跑在 8080 端口,端口不一样,浏览器就会拦截跨域请求。有两种常规解决办法。第一种是后端写一个配置类,实现 WebMvcConfigurer,重写 addCorsMappings 方法,允许指定来源跨域访问。这种方式写起来简单,适合没有前端构建工具的本地调试,或者后端接口要对外提供服务的场景。第二种是前端在开发服务器配置里做转发,Vite 项目在 vite.config.js 里设置 server.proxy,把 /api 开头的请求转发到后端地址。这样对浏览器来说,前端页面和后端接口是同源的,浏览器就不会产生跨域拦截。生产环境部署时,一般由 Nginx 统一接管。前端构建出的静态文件放到 Nginx 的 html 目录,后端在服务器上启动在某个端口,Nginx 配置里把 /api 路径的请求转发到后端端口,其他路径走静态文件。这套部署方式是当前前后端分离项目的标准姿势,熟悉一遍对以后工作也有帮助。6.2 前端路由刷新 404:最经典的部署坑如果你把前端项目部署到 Nginx,然后直接访问某个子路由页面,比如 /cart,刷新一下可能就出现 404。原因是 Vue Router 默认用 history 模式,URL 里没有 # 号,看起来像真实路径,但 Nginx 里并没有这个物理路径,所以就报了 404。解决办法是在 Nginx 配置的 location / 里加上 try_files,让它找不到对应文件时回退到 index.html。这样前端路由自己会匹配对应的页面组件,问题就解决了。这个知识点几乎是部署类问题的必考项,网上源码的部署文档里如果没写配置,你大概率会踩这一脚。6.3 数据库连接与端口占用问题跑通项目时常见的报错有几个。第一个是数据库连不上,报 Access denied for user,检查数据库密码和配置文件里的密码是否一致,检查 MySQL 服务是否已经启动。第二个是端口被占,8080 端口被其他程序占用,可以通过改后端配置文件端口或者找出占用进程来解决。第三个是时区问题,报错里出现 server timezone 字样,在 JDBC 连接串后面加上 serverTimezone=Asia/Shanghai 即可。还有一个特别容易忽略的问题就是数据库版本和驱动版本不匹配。如果 MySQL 是 8.0,而 pom.xml 里的驱动是 5.1 系列的旧版本,启动时也可能报通信链路异常。解决办法是把连接驱动版本换成和数据库对应的版本。6.4 免税商品场景的税价逻辑演示这个项目的业务背景是免税商城,那演示时就要让这块有存在感。我的做法很简单:商品详情页同时展示原价和免税价,免税价的字体更大更显眼,原价加上删除线放在旁边,形成直观对比。购物车结算页里列出“商品总额”和“优惠金额”,优惠金额就是原价总额减去免税价总额的差值,这个数据在创建订单的接口里已经算好并传给前端了。答辩时如果被问到“免税价是怎么算的”,回答思路可以这样组织:商品表里存有原价字段,免税价字段是在上架时由运营人员根据规则填写的,相当于业务规则前置,后端在下单时不关心价格是怎么来的,只关心最终生效价是哪个字段。这套逻辑虽然简单,但它把业务和系统解耦了,就算题库规则怎么变,订单系统都不需要跟着改。还有一点值得提:库存扣减的并发处理。秒杀这类场景我建议不要在这个项目里实现,但可以在论文里提一句“未来可以引入 Redis 缓存和分布式锁来处理高并发下的库存扣减”,这样既不会增加开发工作量,又能体现你的思考深度。我个人在实际操作中的体会是,这类项目真正拉开差距的往往不是功能多少,而是边界处理。你看很多源码,主流程都通,一提交订单就崩,一删数据就报错,一上传图片就找不到路径,全是细节问题。另一个体会是拿到任何源码,先补数据再跑起来,把测试数据做好准备,把演示路径想好,答辩时就能从容很多。最后再分享一个扩展方向:如果你时间充裕,可以在现有商城基础上加一个 Redis 缓存热门的接口,加一个消息队列处理订单通知,或者用定时任务自动取消超时未支付订单。这几个点任何一个做出来都比换个前端框架更有含金量,而且它们都是真实业务里会用到的东西,对你后续找工作面试也有实实在在的帮助。