☰
SpringBoot+Vue免税商城项目实战:从源码到答辩全流程技术解析
2026/10/21 3:13:30 网站建设 项目流程

1. 从"帮人验货"说起:这套免税商城项目的选题价值与技术落点

前阵子有个学弟找到我,说手上有一套"SpringBoot+Vue 免税商品优选购物商城平台"的完整项目源码,带 SQL 脚本和接口文档,问我能不能直接拿去当毕设用。我没急着给答案,而是让他把项目结构、数据库脚本和接口文档三样东西发过来,花了一个晚上整体过了一遍。看完我的结论是:这个选题本身选得不错,技术栈也很稳,但真正拉开差距的地方不在于"能不能跑通",而在于你能不能把里面的设计逻辑讲清楚。这篇文章就把我看这套项目时的技术分析过程写出来——选题到底好在哪里、核心模块是怎么设计的、数据库和接口有哪些门道、从拿到源码到成功答辩之间还要补哪些功课。

如果你正打算用这套项目,或者自己正在写一个电商类毕业设计,这篇文章应该能帮你省掉不少弯路。里面所有结论都来自实际过项目的经验,不涉及具体人、具体学校,你对照自己的环境看就行。

1.1 免税场景比普通电商多出的业务规则

普通商城项目的业务逻辑其实很直白:用户注册登录、浏览商品、加购物车、下单、模拟支付、后台管理商品。这套项目之所以把"免税"两个字加进去,并不是为了名字好听,而是因为它确实带出了一批普通商城没有的数据字段和业务规则。

首先是价格体系更复杂。免税商品通常要同时展示"完税价"和"免税价",商品表里就得有两个价格字段,前端列表页展示的是免税价,详情页要同时对比两个价格,结算时的计价逻辑也跟着走。这就比"只有一个 price 字段"的商城多一层设计。

其次是购买资格的校验。免税购物通常会涉及额度限制和身份信息核验,比如证件类型、出入境记录这类信息,都要落到用户表或者单独的资质表里。注册流程也因此比普通商城多一步,用户模块不能只存一个手机号。

然后是"优选"这个定位。标题里的"优选"两个字意味着商品列表不是全量铺开,而是有精选、推荐、排序的概念。这给首页推荐位、商品列表排序、分类导航都增加了可以发挥的空间,后台也要有对应的字段去标记"优选商品"。

这些规则单独看都不难,但合在一起,就比"随手写一个商城"有内容可讲。答辩的时候老师问"你的项目和普通电商有什么区别",这就是现成的答案。

1.2 项目规模与毕设定位是否匹配

毕业设计的核心要求其实就三条:一是有完整的业务闭环,二是技术栈能用上主流框架,三是工作量自己扛得住、讲得清楚。这套项目属于典型的"中等规模全栈"——后端 SpringBoot 管 API,前端 Vue 做页面,MySQL 存数据,覆盖面刚好卡在课程设计和企业项目之间的中间档。

从实际交付看,一个这样的商城一般涉及 7 到 9 张核心表,20 个左右的接口,前端 10 到 15 个页面。这个量级对一个人来说,从零开发大概要三到四周;拿到完整源码去学习和改,时间还能再压缩。

唯一要提醒的是,毕设评审越来越看重"你自己能不能讲清楚"。源码可以拿来参考,但业务逻辑必须吃透,所以我后面专门用一章讲怎么消化项目,而不是让它只躺在硬盘里。

1.3 拿到项目先看哪三样东西

不管你是从哪个渠道拿到这套完整项目,第一件事不是急着跑起来,而是先检查三样东西。

第一,项目结构。看后端是不是标准 Maven 工程,有没有 pom.xml;前端有没有 package.json,目录层级是否完整。缺了这些,后面导入 IDE 会非常痛苦。有的资源打包时会漏掉前端 node_modules(这是正常的,node_modules 本来就不该打包),但 package.json 必须在,否则没法装依赖。

第二,数据库脚本。看 SQL 脚本里是只有建表语句,还是连初始化数据一起给了。只有建表没有数据,登录进去什么都没有,演示效果会差一大截。初始化数据里尤其要确认有没有管理员账号和一批商品数据。

第三,接口文档。看文档覆盖了哪些接口,和后端代码里的 Controller 是不是对得上。如果文档只是摆设,联调的时候就会很头疼,答辩时候老师翻文档也会发现对不上。

这三样都齐了,再往下走才有意义。缺一样,我建议你优先补齐——因为这几样本身就是答辩时老师会翻的东西。

2. 技术选型推演:SpringBoot+Vue 为什么是毕设主力组合

我不是说这个组合有多先进,而是它确实是目前"把一件事做成"性价比最高的路径。尤其对 Java Web 方向的学生,这套技术栈和课程内容高度重合,老师看着熟悉,学生也好答。选型这东西,最怕的不是技术旧,而是你选了一个自己都解释不清楚的新东西。

2.1 后端版本与依赖怎么选

SpringBoot 现在最常见的两个大版本是 2.x 和 3.x。如果这套项目是基于 SpringBoot 2.7.x 配 JDK 8,那是最稳的组合:资料多、兼容性好、网上踩过的坑基本都有人帮你踩过了。如果是 3.x,那就要求 JDK 17 以上,而且原来的javax.*包要换成jakarta.*。如果你不熟悉这个迁移,导入依赖时会遇到一些莫名其妙的报错。

我个人的建议是:项目原本是哪个版本,就用哪个版本,不要轻易升级。除非老师明确要求用新版本,否则在毕设阶段,"稳定跑通"比"版本新"重要得多。

ORM 方面,这类项目大多用 MyBatis 或 MyBatis-Plus。MyBatis-Plus 的好处是能自动生成单表 CRUD,省掉大量重复的 Mapper XML,分页查询也特别简单——传一个 Page 对象进去就能拿到当前页数据和总数。如果你看到代码里大量使用LambdaQueryWrapper这种写法,那基本就是 MyBatis-Plus 的典型风格,你要学会读懂它,答辩时也能解释它做了什么。

2.2 前端:Vue2 和 Vue3 的分歧怎么选

前端这块,项目用的多半是 Vue 2 + Element UI,或者 Vue 3 + Element Plus 这两条路线。

Vue 2 虽然官方已经进入维护期了,但资料量和坑的友好度都更好,很多模板项目也都是 Vue 2 写的老代码。如果你拿到的源码是 Vue 2,建议老老实实用 Vue 2 配套的依赖版本跑,别强行升级到 Vue 3——否则组件库、路由、状态管理全部要换一茬,工作量瞬间翻倍。

Vue 3 + Vite 的优势是启动快、组合式 API 更现代,适合从零写的新项目,但前提是你对 Node 版本和依赖包版本有把控能力。很多 Vue 3 项目跑不起来,不是代码错,而是 Node 版本太高导致某个原生依赖编译失败。

怎么判断手上的项目是哪种?看package.json里的 vue 版本:2.x 就是 Vue 2,3.x 就是 Vue 3。再看main.js的写法,Vue 2 是new Vue(),Vue 3 是createApp(),一眼就能认出来。

2.3 哪些"高级组件"不必急着上

很多同学拿到项目后的第一反应是:我要加 Redis、加 RabbitMQ、加微服务,显得自己水平高。我的态度很明确:如果只是为了"看起来豪华"而硬塞中间件,答辩翻车的概率反而更大。

原因很简单,毕设答辩的重点是"这个系统是怎么工作的",而不是"你用了多少技术"。你引入 Redis,老师就会问缓存穿透、缓存击穿、缓存一致性;你引入消息队列,老师就会问消息丢失、重复消费、顺序问题;你拆微服务,老师就会问服务发现、负载均衡、分布式事务。任何一个问题说不清楚,都是减分项。

先把这套项目里自带的原生能力吃透,再考虑加东西。后面我专门列了几个低成本高回报的扩展方向,性价比比硬堆中间件高得多。

3. 核心功能模块实战拆解:商品、购物车、订单、用户

接下来是整篇文章最核心的部分。我把这类商城项目的核心模块逐个拆开讲,讲清楚每个模块的数据结构、业务链路和常见设计。你看源码的时候可以按这个顺序去对照,也可以直接拿来做自己项目的设计参考。

3.1 商品模块:免税字段的数据建模

商品模块是整个商城的基座。参考实现里商品表通常叫product或goods,核心字段一般长这样(我用简化的建表语句示意):

CREATE TABLE `product` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '商品ID', `category_id` INT NOT NULL COMMENT '分类ID', `name` VARCHAR(255) NOT NULL COMMENT '商品名称', `subtitle` VARCHAR(255) DEFAULT '' COMMENT '副标题/卖点', `main_image` VARCHAR(500) DEFAULT '' COMMENT '主图URL', `detail` TEXT COMMENT '商品详情', `price_normal` DECIMAL(10,2) NOT NULL COMMENT '完税价', `price_tax_free` DECIMAL(10,2) NOT NULL COMMENT '免税价', `stock` INT NOT NULL DEFAULT 0 COMMENT '库存', `sales` INT NOT NULL DEFAULT 0 COMMENT '销量', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '上下架状态 1上架 0下架', `is_premium` TINYINT NOT NULL DEFAULT 0 COMMENT '优选标识 1优选 0普通', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_category_status` (`category_id`, `status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';

这里有三个设计点值得你专门看。

第一个是price_normal和price_tax_free双价格字段。这是"免税"业务落地的关键,前端列表展示免税价,详情页同时展示完税价和免税价,下单结算按免税价走。如果只有单价格字段,这套项目的"免税"属性就只剩下一个空壳。

第二个是is_premium优选标识。首页推荐位、精选列表基本都是筛选这个字段,再配合sales做销量排序。这就是"优选"语义的直接体现。

第三个是main_image存的是 URL 而不是二进制图片。图片文件一般放在前端项目的静态目录或者对象存储里,数据库只存路径。这样查询压力小,也方便运营换图。

商品分类一般单独建表,前端导航按category_id过滤。对毕设来说,分类表不要设计成无限级别,两级分类足够,用parent_id字段就能搞定,避免递归查询给自己添堵。

3.2 购物车模块:会话方案与前后端链路

购物车有两种主流做法:一种是把数据放前端 localStorage,另一种是放后端数据库表。这套项目有 cart 实体类,多半是后端方案,但我建议两种都理解一下,答辩时能说出各自的适用场景。

前端方案的优点是匿名用户也能加购、实现简单、刷新不丢,但换浏览器、换设备就没了,没法跨端同步。后端方案的优点是购物车数据持久化,用户登录后在任意设备都能拿到,但需要一张购物车表,还要处理"登录了但本地还有旧购物车数据"的合并问题。

毕设项目用后端方案更好讲。购物车表的核心字段就三个:user_id + product_id + quantity,再加一个checked选中状态。接口链路也清晰:加入购物车 → 查看购物车列表 → 修改数量 → 勾选要结算的商品 → 进入结算确认页。

这里有一个容易忽略的细节:勾选状态如果存在数据库里,前端 checkbox 的状态要和数据库保持同步,不要出现"我勾了 A 商品,结算时却把 B 商品也一起下单"的 bug。

3.3 订单模块:状态机与超时处理

订单模块是答辩时最容易出彩、也最容易翻车的部分,因为它的业务链路最长:从购物车生成订单 → 扣库存 → 支付 → 发货 → 完成,中间任何一个环节漏掉,都会被老师抓住。

订单状态一般用数字常量表示,常见状态就这几个:

状态值含义说明
0待支付订单创建成功,等待用户支付
1已支付支付成功(或模拟支付成功后置位)
2已发货管理员后台发货后更新
3已完成用户确认收货或管理员标记完成
4已取消超时未支付或用户主动取消

状态流转要设计成有方向的:0 可以到 1,1 可以到 2,2 可以到 3,0 可以跳到 4,其余状态之间不允许乱跳。代码里建议用常量类或枚举统一收口,而不是在业务代码里散着一堆魔法数字。

超时未支付是电商必被问的点。企业项目一般用延迟队列或定时任务扫描,毕设项目最务实的做法是 Spring 自带的@Scheduled定时任务,每两三分钟扫一次待支付订单,超过时间阈值就自动取消并恢复库存。

创建订单时的库存处理也值得注意。正确做法是在扣库存 SQL 里加一个条件限制:

// 扣减库存时用 stock > 0 做条件,避免库存被扣成负数 int rows = productMapper.reduceStock(productId, quantity); if (rows == 0) { throw new BusinessException("库存不足"); }

对应 SQL 是UPDATE product SET stock = stock - #{quantity} WHERE id = #{productId} AND stock >= #{quantity}。利用受影响行数判断扣减是否成功,这个写法很短,但能证明你考虑过并发场景,属于典型的加分细节。

3.4 用户与登录:JWT 权限控制的完整链路

用户模块看起来简单,其实涉及的安全点不少。重点看三处。

第一,密码不能明文存储。哪怕只是毕设,也要至少用 BCrypt 或 MD5+盐的方式加密。如果初始化脚本里管理员密码是一串明文,记得改成加密后的密文,并把登录校验改成对应的验证方式。

第二,登录状态怎么维持。前后端分离项目里,这套项目多半用 JWT。完整链路是:登录成功 → 后端生成 token(里面加密存 userId 等关键信息)→ 前端把 token 存到 localStorage → 每次请求在 Authorization 请求头带上 token → 后端写拦截器统一校验,把 userId 放进请求上下文 → 前端收到 401 后跳回登录页。

后端拦截器的核心逻辑就是一个 Filter 或拦截器类,白名单放行登录、注册、商品列表、商品详情这些公开接口,其余接口校验 token:

@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token == null || !jwtUtil.verify(token)) { response.setStatus(401); return false; } Integer userId = jwtUtil.getUserId(token); request.setAttribute("userId", userId); return true; }

第三,接口的权限划分。管理员接口(后台商品管理、订单管理)和普通用户接口(购物车、下单)要分开,拦截器或方法注解里做角色判断。至少保证:未登录用户不能访问购物车和订单相关接口,非管理员不能访问后台管理接口。

4. 数据库与 SQL 脚本逐表解读:建库、表结构、初始化数据

SQL 脚本是这类项目最容易"一看就过、一跑就崩"的部分。我建议你拿到脚本后,按下面的顺序逐段阅读,不要直接一把梭执行。

4.1 核心表结构与关系

典型的免税商城表集合大概是这些(以你手上脚本为准,可能略有差异):

表名作用关键字段
user用户表username, password, phone, id_card, create_time
category分类表name, sort
product商品表category_id, name, price_normal, price_tax_free, stock, status
cart购物车表user_id, product_id, quantity, checked
order订单表order_no, user_id, total_amount, status, address_snapshot
order_item订单明细表order_id, product_id, product_name, price, quantity
address收货地址表user_id, receiver_name, receiver_phone, detail

表之间的关系很好理解:user 与 order 是一对多,order 与 order_item 是一对多,product 与 order_item 是多对一。

这里有一个非常关键的设计:订单表里通常会有一个address_snapshot,把下单时的收货信息以快照形式复制一份,而不是下单后去 join 地址表。因为用户之后改了默认地址,不应该影响历史订单的发货信息。这种设计细节,看过一遍之后,下次面试或者答辩都能用上。

订单明细表单独建,而不是在订单表里存商品列表字符串,是为了方便统计:查某商品卖了多少钱、某订单包含哪些商品,都能用 SQL 直接算。

4.2 金额、库存、状态字段的坑

关于字段类型,有几条可以直接当铁律用。

金额一律用DECIMAL(10,2),不要用FLOAT或DOUBLE。浮点数在计算机里是近似存储,0.1 + 0.2 在二进制里是个无限循环小数,做总金额计算会出精度问题。DECIMAL 是精确保存的,适合钱。这在答辩时你甚至可以主动提一句,属于基本功。

库存用INT就够,不需要BIGINT。但关键是扣库存时要用条件更新保证不为负,前面订单模块讲过了。

状态字段用TINYINT加注释,不要用 VARCHAR 存"待支付""已支付"这样的中文。数字状态可读性差一点,但查询快、改动稳。写不写 COMMENT 注释,直接决定了后来的人会不会骂你——反正你现在就是那个"后来的人",看看别人的脚本就知道写好注释有多重要。

时间字段统一DATETIME,建表时给默认值CURRENT_TIMESTAMP,这样代码里连 setCreateTime 都不用写。

4.3 初始化数据的正确导入方式

SQL 脚本一般分三段:先CREATE DATABASE建库,再USE选中库,然后建表、插数据。执行的时候用 Navicat 或命令行都可以:

mysql -u root -p < mall.sql

有两个注意点。

第一,MySQL 8 和 MySQL 5.7 的密码加密方式不同。如果用老版本驱动连 MySQL 8,会报"Public Key Retrieval is not allowed"或者 1251 错误,这时候要么在连接串加allowPublicKeyRetrieval=true,要么调整数据库用户加密方式。这些报错后面我会列到一起讲。

第二,建表语句里如果有utf8mb4,要确保数据库字符集没被改乱,否则中文初始化数据插入后全是问号。连接串里建议带上characterEncoding=utf8,双保险。

初始化数据里最该检查的是管理员账号。很多项目初始管理员密码是 123456,如果数据库里存的是密文,你要先确认前端登录页能正常验证通过,否则后台入口对你来说就是锁死的。

4.4 三张最该建的索引

索引不是越多越好,但下面三个场景是这套项目的高频查询,索引必须有:

  • product(category_id, status):商品列表页按分类浏览时走这个联合索引。
  • order(user_id, create_time):用户中心查自己的订单列表。
  • cart(user_id, product_id):加购时判断"同一用户是否已经添加过同一商品",这个组合建唯一索引,重复加购就做数量累加而不是插入新行。

如果你手上的脚本没建这些索引,数据量小的时候感觉不到,但初始化数据插了几百条商品之后,列表接口就会慢到能明显感知。这也是一个可以写进论文"系统优化"章节的点。

5. 接口文档的设计规范:统一响应、分页约定与前端对接

一套项目能不能顺利跑起来,一半看接口设计规不规范。接口文档在这个项目里的地位,不只是"给前端看",更是你答辩时给老师解释系统架构的最好素材。

5.1 统一响应体的约定

先看后端有没有统一返回类,一般叫Result或ResponseData。一个合格的统一响应体长这样:

public class Result<T> { private Integer code; // 0 表示成功,非 0 表示业务异常 private String message; // 提示信息 private T data; // 业务数据 public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.code = 0; result.message = "success"; result.data = data; return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.code = 500; result.message = message; return result; } }

有了统一响应体,前端就能在 axios 拦截器里统一判断 code,而不是每个接口各写各的业务判断。分页返回的数据结构一般固定为:

public class PageResult<T> { private Long total; // 总记录数 private List<T> list; // 当前页数据 private Integer pageNum; // 当前页码 private Integer pageSize; // 每页条数 }

前端拿到total渲染分页组件,拿到list渲染表格,逻辑非常清晰。

5.2 接口文档与 Swagger/Knife4j

如果项目集成了 Swagger 或 Knife4j,启动后端后访问http://localhost:8080/doc.html(Knife4j)或/swagger-ui/index.html(Swagger)就能在线看接口文档。这对调试和答辩演示都很加分,老师看到你有一个可以实时测试的接口文档页面,印象会好很多。

使用方法也很简单:Controller 上加@Api(tags = "...")注解描述这个类是干什么的,接口方法上加@ApiOperation("...")描述这个接口的核心行为,参数上用@ApiParam标注含义。这样生成的文档不用前端来问,你自己就能测试。

如果项目用的是静态 Markdown 接口文档,重点看三个板块:登录鉴权(哪些接口需要带 token)、商品查询、订单创建。这是主链路,先打通这三个,其余接口都是同类套路。

5.3 前端的 axios 封装与请求拦截

看前端的request.js或http.js。通常的做法是创建一个 axios 实例,设置baseURL和超时时间,然后在请求拦截器里把 token 塞进请求头,在响应拦截器里统一处理 401 跳登录、code 非 0 弹提示:

import axios from 'axios' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = token } return config }) request.interceptors.response.use( res => { const { code, message } = res.data if (code === 0) return res.data return Promise.reject(new Error(message)) }, err => { if (err.response && err.response.status === 401) { // token 失效,清空本地凭证并跳登录页 localStorage.removeItem('token') window.location.href = '/login' } return Promise.reject(err) } ) export default request

这里有个关键细节:baseURL: '/api'走的是前端开发服务器的代理,真正请求是交给vue.config.js里的 devServer.proxy 转发到后端地址的。这个代理配置非常关键。如果后端接口地址是http://localhost:8080,前端页面地址是http://localhost:8081,代理没配好,就会出现经典的跨域报错。

6. 把项目从源码跑起来:启动顺序、环境配置与高频报错

拿到源码之后,真正的考验才刚刚开始。我把一套新环境的启动步骤按顺序列一遍,每个环节都是实际踩出来的经验。

6.1 后端导入与运行

第一步,准备好 JDK。版本跟着 pom.xml 走,2.x 项目用 JDK 8,3.x 项目用 JDK 17。版本不对,启动时直接报UnsupportedClassVersionError。

第二步,用 IDEA 打开后端目录,等 Maven 把依赖下载完。如果下载很慢,把 Maven 的settings.xml配上国内镜像源,速度能差出好几倍。这一个动作能帮你省至少半小时。

第三步,打开application.yml(或 application.properties),核对数据库地址、用户名、密码是否和本机一致。这一步如果跳过,大概率启动时报数据库连接失败。

第四步,先执行 SQL 脚本建库建表,再启动 SpringBoot 主类。顺序不能反过来,因为项目启动时会初始化连接池,表不存在就可能报错。启动成功的标志是日志里出现 "Started Application in x.xxx seconds",默认端口是 8080。这时可以先访问后端接口文档页,确认 API 在线。

6.2 前端运行

前端的步骤比后端更依赖环境版本。

先确认 Node 版本。Vue 2 项目建议 Node 14 到 16,Vue 3 项目建议 Node 16 以上。太新或太旧都可能触发依赖编译失败,如果你用的 Node 20 装 Vue 2 项目报 node-sass 错误,不用怀疑,大概率就是版本不匹配。

然后在项目目录执行npm install。如果失败,看报错是网络超时还是编译错误:网络问题换镜像源,node-sass 编译问题换 Node 版本或者执行npm rebuild node-sass。

接着执行npm run serve(Vue 2)或npm run dev(Vue 3 + Vite),看到编译完成和本地访问地址就成功了。

最后打开浏览器访问前端页面,如果能正常登录、能看到商品列表,恭喜,主链路通了。接下来就是逐个页面点一遍,把购物车、下单、后台管理这些功能都过一遍,发现哪里有 bug 再针对性修。

6.3 五个最常见的启动报错

我把同类项目最常见的报错整理成一张表,你可以直接对照排查:

报错现象大概率原因处理方式
启动时连不上数据库密码错误 / 数据库没建 / 地址写错核对 application.yml 与 SQL 脚本中的库名
页面请求全部 404前端代理没配 / 后端没启动 / 端口不对检查 vue.config.js proxy 的 target 是否指向后端
登录失败或请求没反应token 没签发 / 拦截器放行路径错误看登录接口返回值,确认前端拿到了 token
中文乱码字符集不是 utf8mb4 / 连接串没指定编码连接串加 characterEncoding=utf8,库表和链接字符集统一
CORS 跨域报错前后端不同源且没配代理优先用 devServer.proxy 解决,别在后端到处加 @CrossOrigin

还有一个非常隐蔽的坑:前端项目自己占用的端口被系统换掉了,但代理配置里写死的还是旧端口,结果就是所有请求都转发到了错误地址,登录永远失败。遇到这种情况,把代理 target 改成实际的启动端口即可。

7. 答辩翻车预警与二次开发扩展:让项目从"能用"变成"能讲"

最后这一章我觉得是最重要的。因为代码可以复制,但答辩没法复制。老师一眼就能看出你是真做了还是在背答案,所以你要做的,是把项目的核心逻辑转换成自己的语言。

7.1 答辩老师最可能追问的三个问题

我过手了不少毕业设计相关的评审和指导,高频问题基本固定在这几个方向。

第一个,"登录为什么用 JWT 而不用 Session?"回答思路是:前后端分离场景下,前端可能是浏览器也可能是移动端,JWT 无状态、不依赖服务端存储、适合跨端和横向扩展;Session 需要服务端记录会话状态,跨端和集群部署都不方便。但也要诚实地说出 JWT 的缺点——token 无法主动失效,安全性依赖签名算法,所以登录时要设置合理的过期时间。

第二个,"订单超时未支付是怎么处理的?"回答思路:项目用 Spring 定时任务扫描待支付订单,超过阈值自动取消并恢复库存;如果要优化,可以用延迟队列、支付回调等方式。重点是把"当前实现"和"优化方向"分清楚,别把没做的说成做了。

第三个,"数据库表为什么这样设计?"回答思路:从业务实体出发讲清一对多关系,讲清楚为什么订单要存地址快照,为什么金额用 DECIMAL,为什么建联合索引。这些在前面第四章都讲过,只要你能对着 SQL 脚本指着说,基本不会有问题。

记住一个原则:答不出来的问题,不要硬编。诚实说"目前这个版本没做,如果要做大致会采用什么方案",远比含糊其辞要好。

7.2 低成本高回报的扩展方向

如果时间还有富余,我建议按性价比从高到低做这几个扩展。

第一个是首页数据看板。用一个 ECharts 图表展示商品销量 Top10、最近一周订单趋势,后端加两三个统计接口(按日期分组查订单、按销量排序查商品),前端一个页面搞定。答辩时这个看板非常吸睛,而且原理不复杂,技术栈也还是 SpringBoot + Vue,不会给自己挖坑。

第二个是商品缓存。为热门商品列表加一层 Redis 缓存,先查缓存、缓存没有就回源数据库。做的时候注意缓存过期时间和更新策略。这个扩展要结合前面说的"缓存一致性"问题准备一下,但总体可控。

第三个是模拟支付页面。不要只做一个"点击支付就变已支付"的按钮,而是做一个模拟收款页面,输入支付密码、成功后回调更新订单状态。这个流程非常贴近真实业务,也把订单模块的闭环补完整了。

第四个是收货地址管理。如果项目里地址是下单时手填的,升级成独立的 address 表加默认地址管理,订单创建时从地址列表里选择。这个改动量不大,但结构上更完整,配合前面讲的地址快照设计,答辩时能形成完整的故事线。

做完这些扩展,记得同步更新论文和 PPT,不要代码改了、文档还是旧版,老师一问就对不上。

最后说说我的个人体会。帮学弟看这套项目的时候,我最强调的就是一句话:拿到源码只是起点,不是终点。真正有价值的部分,是你能把每一个模块的来龙去脉想明白——为什么这里有张表、为什么接口要这么设计、订单状态流转的每一步发生了什么。等你能做到合上代码、在纸上画出数据流向和接口清单的时候,这套项目才真正属于你。如果你正在跑这套项目,建议按文章顺序走一遍:先看结构,再看 SQL,然后跑通前后端,最后做扩展和答辩准备。中间遇到报错,对照第六章的表排查,基本都能解决。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询