1. 项目概述
1.1 项目背景与目标
这段时间有不少同学在后台问我,毕业设计的题目选什么比较好,既要能体现技术栈完整性,又要保证开发周期可控。我个人的建议是,像这种"Java Web 在线家具商城设计与实现"就很合适——它是典型的电商类业务系统,业务逻辑不复杂但五脏俱全,前后端都能覆盖到,答辩的时候也有东西可讲。
这里先交代一下项目的整体形态:这是一个基于SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0的前后端分离项目,源码里带完整的数据库脚本、项目文档和部署说明。前端是 Vue3 写的商城页面,包括用户端和后台管理端;后端是 SpringBoot2 提供的 RESTful API,负责商品管理、用户管理、购物车、订单等核心业务。
用这套技术栈的好处非常明显:SpringBoot2 是目前 Java 后端最主流的生产级框架,Vue3 是前端生态里社区最活跃的框架之一,MyBatis-Plus 让数据库操作从繁琐的 XML 映射里解放出来,MySQL8.0 则是当前中小企业用得最多的开源数据库。整套组合在求职简历上写出来也是加分项,因为这个组合直接对应了真实公司的头部技术选型。
1.2 这个项目适合谁来参考
我把这个项目的受众分成了三类:
第一类是准备毕业设计或课程设计的学生。这套系统从需求分析、数据库设计到前后端编码,完整度很高,再加上有配套文档,完全可以作为毕设蓝本。你不需要从零开始想"我要做一个什么系统",而是可以直接在这个骨架上去改、去扩展。
第二类是想系统学一遍前后端分离开发流程的初学者。很多人学了 SpringBoot 和 Vue3 的语法,但不知道怎么把它们串起来。这个项目就是一条完整的链路:前端发请求 -> 后端接收 -> 查数据库 -> 返回 JSON -> 前端渲染,你跟着源码走一遍,比看十篇零散教程都有用。
第三类是想快速搭建一个商城类 Demo 的开发者。比如你想给自己的产品做一个展示加下单的网页,或者想研究电商系统的表结构怎么设计,这套源码可以直接用,省去从零设计的时间。
接下来这篇文章,我不会只停留在"这是一个商城系统"的层面。我会把项目的技术选型逻辑、核心模块的实现方式、部署过程里容易踩的坑,以及源码里那些值得注意的设计细节全部拆开来讲。这篇文章的篇幅会比较长,但每一节都是实操中真正用得上的东西。
2. 技术选型与整体架构设计
2.1 核心框架搭配的逻辑
先说后端。SpringBoot2选得很有讲究。有些人会问,现在 SpringBoot3 都出来了,为什么还要用 2.x?这其实是个很现实的问题。SpringBoot3 基于 JDK17,而很多学校的教学环境、云服务器上的 JDK 版本还停留在 8 或者 11。SpringBoot2 对 JDK8 的支持非常完善,你在本机上装一个 JDK8 就能直接跑起来,不会有版本兼容问题。另外,SpringBoot2 的生态资料极其丰富,遇到任何报错,网上一搜基本都有解决方案,这对学生党来说太重要了。
MyBatis-Plus说它是 MyBatis 的增强版一点都不夸张。它保留了 MyBatis 的灵活性,同时内置了通用 Mapper、通用 Service、分页插件、条件构造器等工具。在这个项目里,商品表、用户表、订单表的单表 CRUD 基本不需要写 SQL,直接继承BaseMapper<T>就能用。举个例子,你要查某分类下的所有商品,用 LambdaQueryWrapper 写一行就能搞定:
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Product::getCategoryId, categoryId); wrapper.orderByDesc(Product::getCreateTime); List<Product> list = productMapper.selectList(wrapper);Vue3作为前端基础框架,和 Vue2 最大的区别在于组合式 API 和响应式系统重写。项目里用到了setup语法糖和reactive/ref来管理状态,页面逻辑的组织方式比 Vue2 的 Options API 清晰很多。特别是商城里购物车这种跨组件共享的状态,用 Vue3 组合式 API 把逻辑抽出来,比混入(mixin)好用得多。
2.2 MySQL8.0 为什么是首选
数据库选择 MySQL8.0,主要看中三点。
第一点是性能。MySQL8.0 的默认存储引擎 InnoDB 经过多年优化,在高并发读写下表现稳定,对商城这种读多写少的场景适配度很高。
第二点是功能。8.0 版本引入了窗口函数、公用表表达式(CTE)等高级查询特性,这在写后台管理报表、统计销售额这类需求时能省不少事。举个例子,你想按月统计订单销售额,用 MySQL8.0 的窗口函数可以写出很简洁的 SQL,而在旧版本里可能需要写复杂的子查询。
第三点是兼容性。8.0 向下兼容,如果你之前在 5.7 上写过 SQL,基本可以无缝迁移。反过来,学习 8.0 的新特性对你将来工作也有直接帮助,因为现在新项目普遍直接用 8.0。
这里有个小坑一定要提醒:MySQL8.0 的默认加密方式改成了caching_sha2_password,而一些比较老的 JDBC 驱动版本不支持这个加密方式,会导致应用启动时报数据库连接失败。解决办法有两个,要么换用 MySQL8.0 对应版本的 Connector/J,要么在创建用户时指定使用mysql_native_password加密方式。后面讲常见问题的时候我会再展开细说。
2.3 前后端分离的架构分层
整个项目分成三层:前端、后端、数据库。
前端是Vue3 单页应用,运行在 Node 环境里的 Vite 开发服务器上。用户访问商城首页、点击商品、加入购物车、提交订单,这些操作都在浏览器端完成,页面通过 AJAX 请求向后端获取数据。
后端是SpringBoot2 RESTful API 服务,端口默认 8080。它不直接渲染页面,只负责提供数据接口。项目源码里可以明显看到 Controller 层、Service 层、Mapper 层的分包结构。Controller 层只做参数接收和结果返回,Service 层写业务逻辑,Mapper 层做数据库操作。这种分层的好处是职责单一,哪一层出问题,直接定位到那一层去排查。
数据库是MySQL8.0,存储所有业务数据。前后端分离架构下,数据库只和后端交互,前端永远不能直连数据库。这个设计既是安全问题,也是架构边界问题。
整个架构的工作流程可以概括为:用户在前端页面操作 -> Vue3 组件里的方法通过 Axios 发起 HTTP 请求 -> 后端 Controller 接收 -> Service 处理业务逻辑 -> Mapper 执行数据库操作 -> 返回 JSON 数据 -> 前端渲染更新页面。
3. 数据库设计与核心表结构
3.1 商城系统的表设计思路
家具商城听上去只是一个垂直领域的电商系统,但它的核心数据模型和通用电商是完全一致的:商品、分类、用户、购物车、订单、订单明细,这六张表是必需品。项目数据库脚本里也是围绕这六张表展开的。
我的经验是,数据库设计阶段别急着写代码,先把表结构画出来。商城类系统的表关系其实很好理解:分类和商品是一对多,一个分类下有很多商品;用户和订单是一对多,一个用户能下很多订单;订单和订单明细是一对多,一个订单包含多个商品项。购物车可以理解为用户和商品之间的一个关联表,带数量字段。
3.2 核心表的字段设计解析
商品表是所有表里信息量最大的。基本字段包括商品名称、商品描述、价格、库存、商品主图、商品分类ID、上架状态、创建时间、更新时间。这里有两个字段值得特别说明。
一个是价格字段的数据类型。商城里的价格永远不要用float或double,因为浮点数在计算机里是近似表示的,0.1 + 0.2 不等于 0.3,这在金额计算里是致命的。正确做法是用DECIMAL(10, 2),不仅能精确表示金额,还能控制小数位数为两位。代码里对应的 Java 类型是BigDecimal。
一个是库存字段,我建议定义成INT,并且在扣库存时要小心并发问题。这个项目里虽然没做特别复杂的分布式锁,但你在写更新库存的 SQL 时一定要用类似UPDATE product SET stock = stock - #{count} WHERE id = #{id} AND stock >= #{count}这样的语句来保证安全。靠应用层先查再改,在并发下一定会出问题。
订单表的设计也有讲究。状态字段order_status用TINYINT存数字,0 代表待付款,1 代表已付款待发货,2 代表已发货,3 代表已完成,4 代表已取消。在代码里用常量类去定义这些状态值,不要在业务代码里出现魔法数字。
订单明细表则记录了订单快照。值得注意的是,订单明细里商品名称、商品价格、商品图片这些字段都是从商品表冗余过来的。为什么要冗余呢?因为商品信息可能后续被修改或下架,但订单的历史快照必须保持成交时的原貌。用户查看历史订单时,即使商品已经被删除,订单明细里的信息仍然完整可用。
用户表除了常规的用户名、密码、手机号、邮箱之外,建议加上用户角色字段role,用于区分普通用户和管理员。密码在数据库里绝对不要明文存储,这个项目里用的是 MD5 加盐的方式。用 Salt + md5 对密码做不可逆处理,即使数据库泄露,也无法直接得到明文密码。
3.3 数据库脚本初始化操作
源码的sql目录下会放一个furniture_mall.sql文件,包含了建库、建表、初始数据的完整 SQL。使用前建议先看一下脚本内容,确认表结构和字段是否符合你的理解,再执行导入。
如果你用的是 Navicat 或者 DataGrip,直接在连接上右键运行 SQL 文件即可。如果是命令行方式,就执行:
mysql -u root -p < furniture_mall.sql这里建议用utf8mb4字符集建库,不要用旧的utf8。因为utf8在 MySQL 里最多只支持 3 字节,像某些特殊符号和生僻字是存不进去的,而utf8mb4是完整的 4 字节编码,能覆盖所有 Unicode 字符。表情符号和第三方登录返回的用户昵称里的特殊字符,用utf8mb4才不会被截断报错。
4. 后端核心功能实现详解
4.1 SpringBoot2 项目的基础配置
项目拿到手之后,第一步是看application.yml配置。这个文件是整个后端能否启动的关键,里面包含了服务端口、数据库连接信息、MyBatis-Plus 配置、文件上传路径等核心配置。
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/furniture_mall?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0我要重点说一下这个配置文件里几个容易被忽略的点。
数据源 URL 里的serverTimezone=Asia/Shanghai是必须加的。MySQL8.0 的 JDBC 驱动对时区敏感,如果不显式指定时区,连接时会报The server time zone value '�й���ʱ��' is unrecognized这样的错误。虽然可以通过修改数据库全局时区来解决,但直接在连接串里指定是最稳妥的做法。
map-underscore-to-camel-case: true这个配置很关键。它让 MyBatis 自动完成数据库字段下划线命名和 Java 属性驼峰命名之间的映射。比如数据库里create_time这个字段,Java 实体类里的属性名是createTime,开启这个配置后就不用写繁琐的resultMap来手动映射了。
logic-delete-field是 MyBatis-Plus 的逻辑删除全局配置。实际开发里,用户下的订单、发布的商品都不建议物理删除,万一误操作或者后面要审计,数据就彻底找不回来了。逻辑删除本质上就是给deleted字段打标记,查询的时候 MyBatis-Plus 自动追加WHERE deleted = 0条件。
4.2 用户模块与登录认证
用户模块是商城的入口。注册接口做的事情包括:校验用户名是否已存在、密码加密、插入用户记录。登录接口做的事情包括:根据用户名查询用户、比对密码、生成 Token 返回给前端。
这个项目用的是JWT(JSON Web Token)做登录认证。JWT 的好处是无状态——服务器不需要保存 Session,用户每次请求都把 Token 放在请求头里带过来,后端通过拦截器验证 Token 的合法性就能知道当前是谁在请求。
Token 生成的核心逻辑如下:
String token = Jwts.builder() .setSubject(user.getUsername()) .claim("userId", user.getId()) .claim("role", user.getRole()) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();这里设置的过期时间是 24 小时,生产环境根据业务需要调整。调短了用户要频繁重新登录,调长了又增加 Token 泄露的风险,一般商城类系统 24 小时是个比较合适的默认值。
后端的登录拦截器也很重要。它继承HandlerInterceptor,在preHandle方法里拦截请求,从请求头的Authorization字段取出 Token 并校验。校验通过就把用户信息放进ThreadLocal,方便后续在 Service 层获取当前登录用户;校验失败就直接返回 401 状态码,避免未登录用户访问需要认证的接口。
这里有个实践中的小坑:某些浏览器在跨域请求时不会自动带上自定义请求头,前端跨域时预检请求(OPTIONS 请求)会被拦截器拦截掉,导致实际请求发送不出去。拦截器里需要判断如果是OPTIONS请求就直接放行。
4.3 商品模块的查询与分页
商品列表展示是商城首页的核心接口。前台需要按分类筛选商品、按价格排序、支持分页,同时只展示已上架的商品。这个接口用 MyBatis-Plus 的分页插件来实现非常简单:
Page<Product> page = new Page<>(current, size); LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Product::getStatus, 1); if (categoryId != null) { wrapper.eq(Product::getCategoryId, categoryId); } if (StringUtils.hasText(keyword)) { wrapper.like(Product::getName, keyword); } wrapper.orderByDesc(Product::getCreateTime); Page<Product> result = productMapper.selectPage(page, wrapper);分页插件需要在项目里先注册一个配置类,把MybatisPlusInterceptor和PaginationInnerInterceptor注入到 Spring 容器中:
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }分页插件切忌不注册直接调selectPage,那样分页不生效,会把全表数据都查出来,一旦数据量上来,接口会非常卡。
商品详情的查询同样值得留意。详情页除了商品本身的信息,还要展示该商品所属分类的名称,而分类信息在另一张表里。这种场景可以写一个自定义的 SQL,用 JOIN 把两张表查出来,映射到专门的 VO(View Object)类里。Controller 层返回给前端的,应该是封装好的 VO,而不是直接把数据库实体类抛出去。原因很简单:实体类里的某些字段(比如库存、内部备注)可能不想暴露给前端,或者数据库字段名和前端需要的 JSON 结构不一致。
4.4 购物车与订单流程
购物车功能本身并不复杂——添加商品、修改数量、删除商品、查询列表。但它有一个很影响体验的细节:购物车里保存的是添加时刻的商品快照,如果商品价格后来变了,用户购物车里的显示价格要不要跟着变?合理的方案是购物车表里只存商品 ID 和数量,查询购物车列表时通过 JOIN 实时关联出商品当前的价格和图片。这样用户看到的始终是最新的价格信息,提交订单时再生成订单快照。商品下架了,购物车列表里也能根据商品状态做标记,告诉用户"该商品已下架"。
订单模块是整条业务链路里逻辑最复杂的。下单接口做的工作包括:
- 从购物车或前端传入的商品列表开始,逐项校验库存是否充足
- 计算订单总金额
- 扣减库存
- 生成订单主表和订单明细表数据
- 清空已下单的购物车项
- 返回订单 ID 给前端跳转支付页
这一串操作有一个关键性质——要么全部成功,要么全部失败。如果只生成了订单但扣库存失败,或者只扣了库存但订单没生成,数据就错乱了。解决办法是在方法上标注@Transactional注解,让 Spring 的声明式事务来保证原子性:
@Transactional(rollbackFor = Exception.class) public Long createOrder(OrderCreateDTO dto) { // 校验库存 // 扣减库存 // 保存订单 // 保存订单明细 // 清空购物车 return order.getId(); }rollbackFor = Exception.class这个是重点。Spring 的事务默认只在遇到运行时异常时才回滚,遇到受检异常不会回滚。如果你漏写rollbackFor,业务里抛了一个受检异常,事务管理器认为"这是被调用方预期的正常流程"而提交事务,后果就是这个方法前面执行了一半的操作已经落库了,数据就错了。
订单状态变更也是一个值得讲的点。用户取消订单、管理员发货、用户确认收货,这些都是对订单状态字段的更新。我建议在代码里用枚举或常量类管理这些状态值,并且状态的流转要有对应的权限控制,比如普通用户不能直接调接口把订单状态改成已发货。
5. 前端 Vue3 项目实战
5.1 前端项目结构与开发环境
前端部分是基于 Vue3 + Vite 构建的。Vite 相比 Webpack 最大的优势是启动速度——开发模式下采用原生 ES Module 加载,冷启动基本 1 秒内完成,改代码后的热更新也是毫秒级的。对开发体验来说提升非常大。
建议本地 Node 环境用 16 或 18 的长期维护版本,装完依赖之后执行:
npm install npm run dev默认端口是 5173。前端开发服务器和后端接口不在同一个端口,所以必须配置开发代理来解决跨域问题。Vite 的配置文件vite.config.js里这样设置:
export default defineConfig({ server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } } })这样配置之后,前端代码里请求/api/product/list,在开发环境会被自动转发到http://localhost:8080/product/list。后端的 Controller 里就不需要额外处理跨域注解了,生产环境部署时也用 Nginx 来配置反向代理,这是一个比较规范的做法。
5.2 前端核心页面的实现要点
商城首页是整个项目里视觉上最复杂的页面。顶部是导航栏,包含 Logo、搜索框、购物车图标和用户登录状态;中间是轮播图和分类导航;下面就是商品瀑布流列表。
商品列表在 Vue3 里的实现是典型的"响应式数据 + 网络请求 + 渲染",用组合式 API 写出来相当清晰:
const products = ref([]) const loading = ref(false) const getProductList = async () => { loading.value = true try { const res = await axios.get('/api/product/list', { params: { categoryId: currentCategoryId.value } }) products.value = res.data.records } finally { loading.value = false } }商品卡片组件里需要注意图片加载的处理。电商页面上图片数量多,如果用<img>一次性全部加载,首屏白屏时间和带宽占用都很难看。这个项目里用到了 Vue3 的自定义指令来实现懒加载,核心逻辑是用IntersectionObserver监听图片是否进入视口,进入后再给src赋值。
购物车页面在 Vue3 里的一个重要技术点是状态管理。购物车数据需要跨页面共享,比如你从列表页加入购物车,再跳到购物车页面就能看到刚加的商品。Vue3 生态里对应的是Pinia。Pinia 可以理解成一个全局的仓库,把购物车的状态和操作封装在useCartStore里:
export const useCartStore = defineStore('cart', { state: () => ({ items: [] }), getters: { totalCount: (state) => state.items.reduce((sum, item) => sum + item.count, 0), totalPrice: (state) => state.items.reduce((sum, item) => sum + item.count * item.price, 0) }, actions: { addItem(product) { // 检查购物车里是否已有同商品,有则数量加一,没有则新增 } } })用unique的方式写好getters,页面上就能直接通过cartStore.totalCount拿到购物车里的商品总数,导航栏上的数量角标就是从这里渲染的,加入购物车后角标数字实时变化。
订单提交页把购物车里选中的商品、收货地址、结算金额整合在一起展示。提交后跳转到支付页面,这部分项目里一般是模拟支付——点击"立即支付"就自动把订单状态从待付款改成已付款。真实支付需要对接微信支付、支付宝支付,属于企业级需求,毕设项目用模拟体验是合理的。
后台管理端用的是独立的布局,左侧菜单栏,右侧内容区。菜单包含商品管理、分类管理、订单管理、用户管理,路由级别做了权限控制,只有管理员账号能进后台。
5.3 Vue3 与 TypeScript 的使用调优
虽然标题里没特别强调 TS,但如果你是用 Vite 创建项目时选择了 TypeScript 模板,源码里会出现不少.ts文件。那需要特别注意类型定义。比如后端返回的接口结构,建议提前定义好:
export interface Product { id: number name: string price: number stock: number imageUrl: string categoryId: number status: number description: string }再比如 Axios 响应拦截器,封装好统一的返回类型。自定义request.ts模块是实践中必须做的一步,不然每个页面请求接口都要重复处理 Token 注入和 401 跳转逻辑。统一的 request 拦截器会在请求发出前自动携带 Token,响应拦截器统一处理返回异常状态码。封装好之后,各页面的组件代码会非常干净。
6. 部署运行与常见问题排查
6.1 从源码到本地运行完整流程
拿到源码之后,无论你是要跑通看效果,还是基于它二次开发,第一步都是把本地环境搭好。我给一个可以照抄的操作顺序。
第一步,安装 JDK8 和 Maven3.6 以上版本。装完后命令行输入java -version确认版本号正常。
第二步,安装 MySQL8.0。Windows 下直接下载安装包,Linux 下注意用 Docker 或者离线安装包,安装完成后确认服务已经启动,能正常用 Navicat 或命令行连上。
第三步,用数据库管理工具执行源码里的furniture_mall.sql脚本,初始化数据库。
第四步,用 IDEA 打开后端项目,等待 Maven 依赖下载完成。修改application.yml里的数据库用户名和密码为你本机的实际值。启动项目前先mvn clean compile验证代码没有问题,然后运行FurnitureMallApplication主类。
第五步,前端项目用 VSCode 或 WebStorm 打开,执行npm install安装依赖,然后npm run dev启动开发服务器。
第六步,浏览器打开http://localhost:5173,如果能正常看到商城首页,说明整个链路已经跑通了。默认的测试账号密码一般会写在项目文档里,如果数据库脚本里有初始化的管理员账户和普通用户,可以直接拿来登录测试。
6.2 后端常见的几个"启动就报错"
后端启动失败的原因,九成以上都集中在数据源配置。我把高频报错整理成了一个排查表:
| 报错信息 | 原因分析 | 解决方案 |
|---|---|---|
Access denied for user 'root'@'localhost' | 数据库用户名或密码不对 | 检查application.yml中spring.datasource.username和password |
Unknown database 'furniture_mall' | 数据库脚本没有执行成功 | 执行 SQL 脚本,或者检查配置文件里数据库名是否正确 |
The server time zone value ... is unrecognized | 没有指定时区 | 在连接串里加上serverTimezone=Asia/Shanghai |
Public Key Retrieval is not allowed | MySQL8.0 默认认证方式问题 | 连接串里加上allowPublicKeyRetrieval=true |
Failed to configure a DataSource | 配置文件没有读取到数据源信息 | 确认使用的是application.yml而不是application.properties,或者补全数据源配置 |
第 4 行那个问题需要多说两句。MySQL8.0 使用caching_sha2_password认证插件时,客户端首次连接需要服务器返回公钥,如果 JDBC 连接串不设置allowPublicKeyRetrieval=true,就会报这个错。解决办法是加参数,或者在 MySQL 里把用户的认证插件改回mysql_native_password:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456'; FLUSH PRIVILEGES;6.3 前端开发中容易踩的坑
先说跨域问题。如果你看到浏览器控制台报No 'Access-Control-Allow-Origin' header is present,基本可以判断是前后端跨域没处理好。开发阶段优先确认 Vite 的 proxy 配置是否生效,看 Network 面板里请求地址是不是/api/xxx这种相对路径。如果你自己反手在后端写了一个@CrossOrigin注解,开发环境可能能用,但到生产环境用 Nginx 反向代理时,前后端同域部署就不需要跨域配置,反而要回退代码,走 Vite proxy 更规范。
再说后端返回的日期格式。Java 后端默认的日期序列化格式是 ISO 8601 的 UTC 时间,比如2024-05-20T09:30:00.000+00:00,前端直接展示体验很差。需要在前端封装dayjs或者在后端配置 Jackson 的日期格式:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8这样后端返回的日期字符串就是2024-05-20 17:30:00这种格式,前端直接渲染即可。
再说一个很容易被忽略的登录状态失效跳转。如果 Token 过期,后端返回 401,前端应该统一跳转到登录页而不是让用户看到一条陌生的报错提示。在 Axios 的响应拦截器里,对 401 状态码做统一处理:
instance.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401) { localStorage.removeItem('token') router.push('/login') } return Promise.reject(error) } )前端导入模板的时候如果样式乱了,多半是组件库版本不匹配。Vue3 生态对应的组件库是 Ant Design Vue 4.x 或 Element Plus,注意不要混用 Vue2 版本的旧库。如果运行时报错类似于Cannot read properties of undefined (reading 'install'),基本就是组件库和 Vue 版本不兼容,卸载重装对应大版本即可。
6.4 部署到生产环境的简要指引
本地跑通之后,有的同学想部署到云服务器上,给答辩做准备或者让同学远程访问。生产环境部署的核心思路是:前端构建成静态文件,交给 Nginx 托管;后端打包成 JAR 包,用java -jar运行;数据库在服务器上也装一个 MySQL。
前端构建命令:
npm run build构建完成后,dist目录下就是纯静态文件。把这些文件上传到服务器的 Nginx 站点目录,并在 Nginx 配置里把/api开头的请求反向代理到后端的 8080 端口:
server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }注意try_files $uri $uri/ /index.html这一行很关键。Vue Router 如果用的是 history 模式,用户直接访问某个子路由路径(比如刷新商品详情页),Nginx 会先去找服务器上有没有这个真实文件,找不到就重定向到index.html,让前端路由自己处理。不写这一行,部署后刷新子页面会直接 404。
后端打包时如果遇到 Scala 相关的 Maven 插件报错,多半是 IDEA 自动引入了不需要的依赖,mvn clean package前检查一下pom.xml有没有多余插件。
7. 文档编写与答辩准备技巧
这个项目的标题里特意带了"含文档"三个字,说明文档质量是这类交付物的核心竞争力之一。
毕设文档一般包含几个固定章节:绪论、需求分析、系统设计、数据库设计、系统实现、系统测试、总结与展望。
需求分析部分要写清楚系统有哪些角色、每个角色能做什么操作。这个项目里角色很清晰:游客可以浏览商品但不能下单,普通用户可以登录注册、浏览商品、管理购物车、下订单、查看订单状态,管理员可以管理商品分类、上下架商品、处理订单、管理用户。
系统设计部分要画系统架构图、功能模块图、业务流程图(比如下单流程、登录流程)。这部分建议从项目的实际代码出发,画出真实的结构,而不是套模板瞎写,答辩时老师一问细节就穿帮了。
数据库设计部分是老师重点看的地方。要把每张表的字段名、类型、含义、外键关系列清楚,同时解释一下为什么要这样设计,比如为什么订单明细要冗余商品信息、为什么价格用 DECIMAL 不用 FLOAT。这些设计上的思考是能体现你真懂这个项目的关键。
系统实现部分可以按模块来讲。每个模块讲清楚:这个模块是干什么的、用了什么技术、核心代码怎么写的、关键逻辑怎么实现的。比如订单模块就讲事务控制、库存扣减、订单状态流转这三个点。
系统测试部分要包含功能测试用例表,把每个核心功能点的操作步骤、预期结果、实际结果列一下。如果时间充裕,可以再加一份简单的性能测试记录。
答辩的时候,老师通常会问这几个问题:项目里最难的点是什么、你怎么解决的?购物车的数据存在哪里?订单的事务是怎么保证的?Token 过期了怎么办?你在做项目的时候遇到过哪些坑?这些问题其实都是平时开发过程中真实会遇到的问题,所以平常自己动手写过、踩过坑的同学,回答起来根本不虚。
8. 项目扩展建议与二次开发思路
如果这个项目已经跑通了,你还想让它变得更有竞争力,这里有几个成本不高但效果明显的扩展方向。
第一个方向是接入真实的第三方登录。现在叫一个不做微信登录的商城,总感觉不够完整。你可以对接微信开放平台或者用一个开源项目给前端加个扫码登录的入口,外行人一看便觉得"这项目很完整"。
第二个方向是改成 Redis 做缓存。商品分类和热门商品列表这种读多写少的数据,放进 Redis 里能明显提升接口响应速度。本地用 Docker 拉一个 Redis 镜像,后端加一个 Spring Boot Redis 依赖,改改 Service 层代码就能缓存查询结果。在简历上写"使用 Redis 对热门商品接口进行缓存优化,QPS 提升明显",效果很直接。
第三个方向是增加数据可视化面板。后台管理端的首页目前可能只是一个简单欢迎页,你可以用 ECharts 接一个销售额趋势图、商品销量排行、订单分布图。ECharts 和 Vue3 的集成非常友好,按官方文档写一个封装组件,用后端统计接口返回的数据喂给图表即可。
第四个方向是部署上云加 CI/CD。如果服务器年费预算有限,可以先在本地用 Docker 把项目容器化,写一个简单的Dockerfile,再到服务器上用 Docker Compose 编排容器。面试官看到你简历上写了"熟悉 Docker 容器化部署,掌握 Nginx 反向代理与自动化部署流程",能压过不少同级竞争者。
这个项目的价值不在于它本身有多大的商业规模,而在于它把一套主流的前后端分离技术栈完整地揉在一起,让你有机会真正走一遍从设计到实现再到部署的项目全流程。
我个人的体会是,做这类项目最忌"糊里糊涂跑通"——你只是把代码拉下来,双击启动,看到页面能出数据就觉得自己会了,答辩时一被追问就垮。正确的方式是自己动手把核心代码从头理解一遍,然后找三四个点改一遍、重构一遍。哪怕只是把购物车改成 Redis 存储、给订单模块加一个简单的超时自动取消,都比把现成代码无缝跑通有价值得多。开发这件事,能力是写代码写出来的,不是看代码看出来的。