ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Spring Boot毕设实战:做一套完整水果购物电商系统

Spring Boot毕设实战:做一套完整水果购物电商系统 每年到毕设季后台私信里总挤满了有没有现成的Java项目可以参考这种话。我做过的 Spring Boot 毕业设计项目不止一个飘香水果购物网站算是我做得比较满意的一版。这不是那种几十行代码糊弄事的半成品而是一个业务闭环完整的 B2C 电商系统从用户注册、商品浏览、购物车、下单支付到后台的商品管理和订单处理整条链路都是通的。源码和 SQL 脚本我已经整理好了免费分享出来需要的同学可以直接拿去参考、二次开发。这篇内容我会讲清楚几个维度项目选了哪些技术为什么这么选数据库表是怎么设计的核心业务代码——尤其是下单和库存扣减——到底该怎么写前端页面怎么跟后端对接最后是我实际运行过程中踩过的坑以及论文、答辩准备的一些建议。内容偏实操按顺序看完基本能自己把项目从零搭出来。1. 这个选题到底香在哪里1.1 为什么选水果购物网站而不是千篇一律的管理系统毕设选题最怕什么不是题目难而是做完之后连自己都讲不清这个项目到底解决了什么问题。很多同学选各种管理系统——学生管理系统、图书馆管理系统、员工考勤系统代码写完之后天天就是对几张表做增删改查答辩时老师问一句业务难点在哪全场安静。购物网站项目就不太一样。电商天然是一个有商品、有用户、有流程、有状态的业务场景系统里有明显的数据流转用户浏览商品、加购物车、下单、支付、后台发货、用户确认收货。这一条链路串下来可讲的东西一下子就有了层次从需求分析到数据库设计从接口调试到事务处理每一步都有实实在在的业务逻辑在里头。再说选水果这个品类其实也是个小心机。水果有分类热带、时令、进口有时效性和库存概念可以做折扣、做限时抢购、做销量排行甚至还能写一个简单的根据销量推荐热卖水果的小功能。这些业务细节会让系统看起来更完整答辩时讲故事也能讲得更生动。见过太多人做手机商城服装商城水果购物网站反而让人眼前一亮。1.2 技术选型背后的逻辑为什么 Spring Boot 是毕设首选Spring Boot 能成为 Java 毕设的事实标准不是没有原因的。传统 SSM 项目要写一大堆 XML 配置光是 Spring、SpringMVC、MyBatis 之间的整合就够折腾几天一不小心还配置冲突。Spring Boot 最大的价值是把框架整合的痛苦降到最低内嵌 Tomcat 服务器一个java -jar就能起服务。你做毕设遇到什么情况最多答辩演示时环境出问题。Spring Boot 的一键启动特性直接帮你把翻车概率降下来一大截。但版本选择上有个很现实的坑Spring Boot 3.x 必须配合 JDK 17 使用而且很多第三方依赖在新版框架下要做兼容适配Spring Boot 2.7.x 则比较仁慈JDK 8 就能跑。我实际见过太多同学电脑上装的 JDK 8却用骨架生成了 Spring Boot 3.x 的项目最后连编译都过不去还一脸懵。注意先确认 JDK 版本再选 Spring Boot 版本。如果学校机房统一是 JDK 8老老实实选 Spring Boot 2.7.x如果自己电脑装的是 JDK 17 或更高再考虑上 3.x。别小瞧这个选择它决定了你后面几天是顺利还是踩坑。除了 Spring Boot项目里还用了这些核心组件MyBatis-Plus负责数据库操作手写 SQL 可控性更好答辩时聊 SQL 也接得住MySQL 8.0主数据库Redis做登录会话缓存和热点数据缓存Vue3 Element Plus搭前台页面和后台管理页面MinIO可选做商品图片文件存储。1.3 整体功能地图用户端和管理端各做什么项目按角色分为两端我习惯叫用户端和管理端也可以用前台/后台来理解。用户端前台核心模块注册登录手机号或用户名加密码登录后用 token 维持会话首页展示轮播图、热卖水果、新品推荐、分类导航商品模块按分类浏览、关键字搜索、商品详情页展示价格、库存、销量、详情介绍购物车加购、修改数量、删除商品、勾选结算订单模块确认订单、模拟支付、订单列表、订单详情、取消订单个人中心收货地址管理、头像上传、个人信息修改。管理端后台核心模块仪表盘简单统计今日订单数、销售额、商品总数等商品管理商品添加、编辑、删除、上下架状态切换、库存调整分类管理商品分类的增删改支持一级分类订单管理订单列表、查看订单明细、订单发货操作用户管理查看注册用户列表、禁用或启用用户。两端共用同一套后端接口只是权限不同。用户和管理员各自登录到不同角色后台接口通过拦截器校验权限。2. 从零搭起环境准备与项目初始化2.1 开发环境版本搭配建议先说版本因为毕设项目七八成的坑都出在环境上。我推荐的方案是这些组件版本建议关键说明JDK8 或 17JDK8 配 Boot 2.7.xJDK17 配 Boot 3.x别混搭Maven3.8 及以上配置阿里云镜像下载依赖速度会明显提升MySQL8.0连接串里要加时区参数否则会报错Redis5.0 及以上Windows 下可以直接启动官方提供的压缩包Node.js16 及以上前端构建需要Vue3 Vite 依赖这个版本IDEA2022 或更高社区版免费完全够用这里有个容易忽略的点Maven 一定要配置国内镜像。默认中央仓库在国内下载依赖极慢一个项目拉几百个 jar 包卡到怀疑人生。配置方法很简单在~/.m2/settings.xml里加入阿里云镜像地址即可具体写法网上随手就能搜到这里不贴了。2.2 创建 Spring Boot 项目骨架建项目用两种方式任选一是 IDEA 内置的 Spring Initializr打开 File - New - Project - Spring Initializr二是直接用浏览器打开 start.spring.io 在线生成然后解压导入 IDEA。依赖勾选建议Spring Web提供 MVC 能力Controller 层靠它MySQL Driver连接数据库的驱动MyBatis-Plus如果用的 Boot 3.x记得引入mybatis-plus-spring-boot3-starter版本Lombok简化实体类代码Validation参数校验Spring Data Redis接入 Redis。骨架拉下来以后用 IDEA 打开先把 Maven 仓库刷完。第一次导入项目会慢但只要镜像配置没问题基本上几分钟就能完成。刷完依赖之后不要急着写业务代码先把工程分层建好。我的分包结构是这样的com.fruitshop ├── controller // 接口层 ├── service // 业务逻辑层 ├── mapper // 数据访问层MyBatis-Plus ├── entity // 数据库实体类 ├── dto // 请求/响应对象 ├── config // 配置类拦截器、跨域、静态资源映射 ├── common // 统一返回结果、业务异常、常量 └── utils // 工具类订单号生成、JWT等这种分包方式主流、清晰老师看代码印象也会更好。2.3 application.yml 核心配置项目跑起来以后第一件事就是把配置文件写好。下面是我实际用到的一套核心配置以 Spring Boot 2.7.x MySQL 8.0 为例server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/fruit_shop?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 你自己的密码 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 password: database: 0 servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl很多人在连接串这里踩坑我提醒三点serverTimezoneAsia/Shanghai不写MySQL 8.0 会报时区错误useSSLfalse建议加上本地开发时减少不必要的握手干扰allowPublicKeyRetrievaltrue不写MySQL 8.0 会报一个非常经典的错误具体名称下面踩坑环节再讲。MyBatis-Plus 的map-underscore-to-camel-case开启后数据库的user_name能自动映射到实体的userName省掉一大堆手写映射。log-impl设置成 StdOutImpl控制台会直接打印 SQL调试时非常有用答辩现场也能给老师展示你的 SQL 日志。3. 数据库设计与核心代码实现3.1 核心表设计数据库设计是整个项目的地基。我第一次做这个项目的时候一开始只设计了六张表后来发现少了一张地址表又从订单表里拆出来反复改了好几次代码。每次改表的教训都是初期必须把表结构设计完整再动手写代码。这个项目最终用的是八张表表名说明核心字段user用户表id, username, password, nickname, phone, avatar, role, statuscategory商品分类表id, name, sort, create_timeproduct商品表id, category_id, name, image, price, stock, sales, status, detailcart购物车表id, user_id, product_id, quantity, checkedorders订单表id, order_no, user_id, total_price, status, receiver_name, receiver_phone, receiver_address, create_time, pay_timeorder_item订单明细表id, order_id, product_id, product_name, product_image, price, quantityaddress收货地址表id, user_id, name, phone, province, city, district, detail, is_defaultbanner轮播图表id, image, link, sort, status这里面最关键的是 orders 表和 order_item 表。一张订单对应多条订单明细通过 order_id 关联这是电商系统最标准的主从表结构。为什么订单明细里要冗余商品名称和图片因为商品表里的信息可能会变但订单是历史数据应该保留下单那一刻的商品快照。这个细节在答辩时特别加分能体现业务理解。order_no 订单号要唯一我用的生成规则是时间戳加用户ID后四位加随机三位数拼接完存进数据库并加唯一索引防止并发场景下重复。3.2 统一返回结果与异常处理接口设计上强烈建议项目一开始就定好统一的返回格式。前端的 axios 请求封装也要依赖它。我的返回体设计如下{ code: 200, message: 操作成功, data: {} }对应的 Java 类Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.setCode(200); r.setMessage(操作成功); r.setData(data); return r; } public static T ResultT error(Integer code, String message) { ResultT r new Result(); r.setCode(code); r.setMessage(message); return r; } }再配一个全局异常处理器用RestControllerAdvice捕获业务异常和参数校验异常Controller 里就不用到处写 try-catch 了。全局异常处理的好处是不管哪里报错返回给前端的格式都一致前端统一弹出错误提示。RestControllerAdvice public class GlobalExceptionHandler { ExceptionHandler(BusinessException.class) public ResultVoid handleBusinessException(BusinessException e) { return Result.error(e.getCode(), e.getMessage()); } ExceptionHandler(MethodArgumentNotValidException.class) public ResultVoid handleValidException(MethodArgumentNotValidException e) { return Result.error(400, e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); } }这个阶段别忘了用 Lombok实体类上都加Data自动生成 getter/setter代码量会少很多。3.3 下单流程怎么保证数据一致性购物网站最核心的业务也是答辩时最高频被问的一个点就是下单。先描述场景用户从购物车勾选商品点击结算后端需要做四件事——生成订单、生成订单明细、扣减库存、清空购物车。这四件事必须在一个事务里完成任何一步失败都不能留下脏数据。我写的核心下单逻辑大概是这样的Transactional(rollbackFor Exception.class) public Order createOrder(OrderCreateRequest request) { // 1. 校验用户地址 Address address addressMapper.selectById(request.getAddressId()); // 2. 生成订单号 String orderNo generateOrderNo(userId); // 3. 保存订单主表 Order order new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setStatus(OrderStatus.PENDING_PAYMENT); order.setReceiverName(address.getName()); // ... 设置商品总价、支付时间等 orderMapper.insert(order); // 4. 遍历购物车勾选项写入订单明细并扣库存 for (CartItem item : cartItems) { Product product productMapper.selectById(item.getProductId()); // 条件更新库存防止并发超卖 int rows productMapper.deductStock(product.getId(), item.getQuantity()); if (rows 0) { throw new BusinessException(500, 商品【 product.getName() 】库存不足); } OrderItem orderItem new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setProductId(product.getId()); orderItem.setProductName(product.getName()); orderItem.setProductImage(product.getImage()); orderItem.setPrice(product.getPrice()); orderItem.setQuantity(item.getQuantity()); orderItemMapper.insert(orderItem); } // 5. 清空购物车对应商品 cartMapper.deleteByUserIdAndProductIds(userId, productIds); return order; }这里重点说一下库存扣减的 SQL。如果只是简单写UPDATE product SET stock stock - #{count} WHERE id #{id}并发下单时会出现超卖问题——两个请求同时读到同一个库存余量各自都扣了一次库存直接扣成负数。我用的写法是加一个stock #{count}条件UPDATE product SET stock stock - #{count} WHERE id #{id} AND stock #{count}通过 UPDATE 影响行数判断返回 0 说明库存不足抛业务异常让整个事务回滚。这样实现简单压力测试下也不会超卖对新手很友好不需要引入复杂的锁机制但能在答辩时清楚讲出为什么这样设计——这就是加分项。3.4 登录鉴权拦截器加 Redis 会话登录这块我采用的是比较主流的 token 方案用户登录成功后服务端生成一个随机 token以token为 key、userId为 value 存进 Redis并设置过期时间比如 30 分钟。之后前端每次请求都带上这个 token后端用一个拦截器统一校验。为什么用 Redis 不用 session主要原因两个一是前后端分离模式下session 跨域处理比较麻烦二是 Redis 天然支持过期时间token 过期管理简单以后项目要扩容部署多台服务器Redis 里的会话状态也能共享。拦截器示例public class LoginInterceptor implements HandlerInterceptor { private final StringRedisTemplate redisTemplate; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (StringUtils.hasText(token)) { String userId redisTemplate.opsForValue().get(token: token); if (userId ! null) { request.setAttribute(userId, userId); return true; } } response.setStatus(401); return false; } }再把拦截器注册到 WebMvcConfig 里排除掉登录注册、商品列表、商品详情等公开接口其他接口统一走拦截器。这里要注意管理员接口路径建议统一以/admin开头再单独写一个管理员权限拦截器专门校验登录用户是不是管理员角色。两个拦截器分开写职责更清晰扩展性也好很多。4. 前端页面与文件上传让项目能看比能跑更重要4.1 前端技术选型与页面结构毕设项目如果只把后端接口做齐是撑不住分数的。老师打开浏览器看到页面还是上个年代的风格第一印象就大打折扣。我的建议是用 Vue3 Element Plus。Element Plus 组件颜值在线表单、表格、弹窗、分页都有现成组件后台管理页面写起来效率极高前台页面也可以拿它做基础风格。前端工程分两个入口用户端页面首页、商品列表、商品详情、购物车、结算页、订单列表、个人中心管理端页面登录页、仪表盘、商品管理、分类管理、订单管理、用户管理。路由结构简单说一下用户端走/下的公共路由管理端走/admin下的路由。管理端路由统一挂一个requiresAdmin的路由守卫登录后没有管理员标识的跳回用户端首页。这个小设计在答辩演示时很加分因为可以现场演示普通用户访问不到后台接口这个权限控制效果。4.2 前端如何和后端无缝对接前端发请求我用 axios统一封装一个 request 工具配置baseURL和拦截器请求拦截器里带上 token响应拦截器里统一处理 401未登录、500业务异常、200正常返回 data。这样每个页面调用接口的时候代码可以写得很干净不用每个接口都重复一遍错误处理。有一点必须提醒开发阶段会遇到跨域问题。解决方式有三种任选其一后端配置CrossOrigin或全局 CORS 配置前端在 Vite 里配置代理直接把前端打包产物放进 Spring Boot 的静态资源目录同源访问彻底避开跨域。我自己最终采用的是第三种因为省心部署也方便。4.3 前端打包放进 Spring Boot之前不少人问过vue 打包放进 springboot 中这个问题看起来基础实际坑不少。我再把踩过的坑讲一遍。前端工程执行npm run build之后dist目录里会生成index.html和assets等文件夹。把这个目录下所有内容复制到 Spring Boot 的src/main/resources/static目录下然后重新打包启动 Spring Boot访问http://localhost:8080/index.html就是前端页面接口同源CORS 问题直接消失。注意打包前确认 Vite 配置里的base是相对路径./否则部署后静态资源会找不到。前端路由建议用 hash 模式也就是createWebHashHistory这样部署到静态目录后刷新页面不会出现 404。4.4 商品图片上传与 MinIO 整合商品要有图片图片就得有存储方案。最简单的方式是本地存储Spring Boot 配置一个静态资源映射目录上传的文件写入磁盘通过http://localhost:8080/upload/xxx.jpg访问毕设演示场景下完全够用。如果想在项目里体现更强的工程能力可以整合 MinIO。用 Docker 一条命令就能起一个 MinIO 服务docker run -p 9000:9000 -p 9001:9001 minio/minio server /data --console-address :9001然后在 Spring Boot 里加入依赖io.minio:minio配置好 endpoint、accessKey、secretKey、bucketName写一个简单的FileStorageService上传文件时生成唯一文件名返回可访问的 URL。MinIO 的好处是文件不占应用服务器磁盘服务重启不丢数据结构也更接近生产环境。时间充裕的同学建议加上答辩时老师问图片存哪里你讲一套对象存储方案层次一下子就上去了。5. 踩坑实录我跑这个项目时遇到的高频问题5.1 环境与版本类问题这类问题占了所有问题的六成我把高频问题整理成速查表问题现象原因解决方案MySQL 连接报错提示 Public Key Retrieval is not allowedMySQL 8.0 默认认证插件需要公钥连接串加allowPublicKeyRetrievaltrue启动报时区错误数据库时区与本地不一致连接串加serverTimezoneAsia/Shanghai或执行set global time_zone8:00项目启动后端口被占用上一个进程没退出改server.port或找到对应进程并结束依赖下载超慢Maven 没配国内镜像在 settings.xml 配置阿里云 mirror导入项目后一堆红叉Maven 仓库损坏或版本冲突执行mvn clean install并刷新 Maven 仓库5.2 代码与配置类问题除环境外代码层面的坑也不少。第一个是 MyBatis-Plus 的自动填充时间字段。很多人新建商品时发现create_time是 null原因是没有配置 MetaObjectHandler或者数据库里的默认值没设置好。我的建议是双保险数据库所有时间字段都设置默认值CURRENT_TIMESTAMP实体里用TableField(fill FieldFill.INSERT)配合自动填充处理器。第二个是前台上传图片后刷新页面图片 404。这通常是 Spring Boot 静态资源映射没配好。上传目录在磁盘上时需要在配置里加资源映射Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file:/绝对路径/upload/); } }第三个是 JSON 序列化报错比如 LocalDateTime 字段返回给前端变成一段很丑的数字。解决方法是配置 Jackson 时间格式spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai5.3 部署与演示前的最后检查清单答辩演示当天最容易翻车的是演示前才发现某些功能没法用了。我整理了一份每次演示前必过的检查清单照着走一遍基本不会出意外启动 MySQL 和 Redis确认端口正常用初始化 SQL 重建数据库保证数据是最新状态启动 Spring Boot 后检查控制台没有异常日志用两个浏览器分别测试用户端和管理端的登录走一遍核心流程注册浏览商品加购物车下单模拟支付后台发货提前准备几个演示账号和若干条商品数据最好包含一个库存极低的商品现场演示库存不足的异常提示反而更有看点如果学校机房网络不稳定提前确认前端已经打进后端离线也能完整演示准备一张项目目录结构截图和数据库 ER 图放 PPT 里避免现场打开 IDEA 找文件时干等。6. 论文与答辩把代码变成分数6.1 毕业论文结构怎么搭很多代码能力强的人论文反而是短板。其实毕设论文有很成熟的结构模板按照学校要求套就行我建议的框架是这样章节内容大致篇幅第一章 绪论背景与意义、国内外研究现状、主要工作4000字左右第二章 相关技术介绍Spring Boot、Vue、MySQL、Redis 等3000字左右第三章 需求分析可行性分析、业务流程、功能需求、非功能需求、用例图4000字左右第四章 系统设计架构设计、功能模块图、数据库设计ER 图和表结构5000字左右第五章 系统实现每个核心模块的实现截图加关键代码片段5000字左右第六章 系统测试测试环境、功能测试用例、测试结果3000字左右总结与展望总结项目亮点和不足1000字左右数据库设计这一章别只贴建表 SQL一定要画 ER 图用 PowerDesigner 或 draw.io 都可以画清楚主从表关系。系统实现这一章不要整段贴大段代码挑核心逻辑——下单事务、拦截器、库存扣减——贴小段关键代码加解释就够了。论文查重时代码块一般不计重复但文字描述一定要用自己的话组织别直接抄开源项目的 README。6.2 答辩演示的黄金十分钟答辩一般就十分钟左右节奏很关键。我自己的演示顺序是固定的分享给各位参考第一步用一分钟讲清楚项目做什么飘香水果购物网站一个基于 Spring Boot 的 B2C 电商系统用户能购买水果管理员能管理商品和订单。第二步用两分钟讲技术亮点前后端分离、Redis 做会话与缓存、MyBatis-Plus 手写 SQL 扣库存、事务保证订单数据一致性、MinIO 做对象存储。第三步用四分钟现场演示重点走一条主线在前台注册一个新账号浏览水果加购下单模拟支付然后切到后台管理端发货同时穿插演示商品搜索、购物车数量修改、订单状态变化这些细节。第四步用两分钟展示数据库设计和代码打开 ER 图和核心表再打开订单 Service 的Transactional代码讲清楚为什么这么写。剩下的时间留给老师提问。平时最常被问的问题我顺手整理了一遍为什么订单表要单独存商品快照——商品信息可能变化订单必须保留历史数据库存扣减怎么防超卖——条件更新加事务回滚Redis 里存了什么——登录 token 和首页热卖商品的缓存项目有哪些不足——支付是模拟的、缺少更精细的权限校验、前端没有做单元测试。承认不足但要强调我知道如何改进可以说下一步可以接入真实支付沙箱、引入 Spring Security 做更细粒度授权。写在最后一点真实的项目心得这个项目我做完花了大约两周的正常课余时间算上写论文的话四周左右。回头复盘最值得分享的经验是先画表再写代码先把接口定好再对接页面。我前期因为贪快表设计改过两次导致后面 Service 层代码也连带重写。这个教训后来每次带队做项目都会提。如果你准备拿这个项目做毕设建议不要直接全部照抄可以把品类换成别的、加上一个评论功能或者设计一个优惠券模块改动之后系统就是你的了答辩的时候你能讲得更有底气。需要完整源码和初始化 SQL 的同学可以按这篇步骤自己搭一份也可以留言给我。我再补一句做毕设的过程本身比分数更重要等你把下单事务、库存扣减、权限拦截这几个点真正讲明白你能明显感觉到自己确实入门了 Java 企业级开发。祝顺利。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进