Spring Boot+Vue前后端分离毕设:二手交易平台核心技术与实践解析
2026/9/13 13:26:17 网站建设 项目流程

简介:基于Spring Boot的二手交易平台项目源码,面向Java后端开发学习者及毕业设计人员,覆盖商品发布、浏览、交易、订单管理等典型业务场景,可作为课程设计与毕设改造基础。压缩包含812个文件,约16.66MB,其中Java源码实现后端业务逻辑,Vue与JS构建管理端及前台页面,HTML/CSS呈现静态样式,SQL文件提供数据库初始化脚本,另有XML映射、Maven配置等辅助文件,整体目录结构清晰,便于按模块查阅。目前已有152人学习下载。资源附带安装/运行批处理脚本、mvnw等工具,配合JDK1.8、Tomcat7、MySQL5.7环境即可搭建运行,能帮助读者省去环境配置与项目整合时间,专注理解二手交易系统的前后端交互与Spring Boot工程实践。

1. 毕业设计选型:为什么是 springboot + Vue 的前后端分离

一个看似普通的二手交易平台题目,真正拆开代码时才会发现,仓库里藏着不少比业务逻辑更值得讲的东西:IndexAsideStatic.vue、IndexHeader.vue 这类文件说明它是带管理后台的前后端分离项目,而mvnw.cmd1-install.bat2-run.bat又暗示作者用 Maven Wrapper 和批处理脚本把环境依赖做了收敛。比较反直觉的是摘要里的 Tomcat7:如果你用 Spring Boot 2.x 打成 war 包丢进 Tomcat7,Servlet 版本和@WebServlet扫描会对不上;但如果只跑 jar 包,Tomcat 版本跟运行完全无关。这篇博文会从实体关系、本地启动、交易核心链路和答辩安全自查四个角度,把这个毕设真正值钱的细节拆出来。

2. 实体关系、目录结构 与 Spring Boot 自动装配原理

2.1 用户、商品与订单的最小实体集合

绝大多数二手交易平台项目都不需要过度设计,核心就是用户、商品、订单三类表,再加上一个分类表和一个轮播图表就够了。用户表里除了idusernamepassword还要有avatarphone,因为前端管理后台的 IndexHeader.vue 组件里一般会显示用户头像和昵称,后端接口如果没返回这些字段,页面就会空一块。

商品表是最容易埋坑的地方。二手商品的特殊性在于“可议价”和“状态多变”,所以字段至少要有goods_namegoods_descpriceoriginal_pricecover_imagestatusseller_idbuyer_idend_time。这里的end_time是很多毕设忽略的字段,学生答辩时被问“商品怎么下架”往往答不上来,后面第 5 章我会专门讲定时下架的实现。

订单表建议把快照字段放进去,比如order_nogoods_idseller_idbuyer_idamountstatuscreate_time。因为二手商品的商品信息会被编辑,订单如果只关联goods_id,用户下单后再看订单详情,看到的是修改后的商品描述,这在演示时很容易被老师质疑。在orders表里冗余一份goods_nameamount快照,逻辑上更合理。

2.2 Spring Boot 自动装配在模块边界上的作用

很多人在答辩时会背“Spring Boot 简化配置”这句话,但被追问“为什么引入spring-boot-starter-web就会有 Tomcat 和 SpringMVC”就卡住了。要讲清楚,得看@SpringBootApplication里面的三个注解:@SpringBootConfiguration标记配置类、@ComponentScan扫描当前包及其子包、@EnableAutoConfiguration负责读取META-INF/spring.factories中的自动配置类列表。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration

这段配置是 Spring Boot 2.x 里自动装配的入口文件内容截断示意。真正起过滤作用的是每个自动配置类上的@ConditionalOnClass@ConditionalOnMissingBean这类条件注解。也就是说,你 classpath 里有DataSource相关类且没有手动定义DataSource,Spring Boot 才会帮你创建数据库连接池。理解这一点后,就知道为什么自定义拦截器或配置类要放在 Spring Boot 启动类的子包里,否则@ComponentScan扫不到,自动配置也不会退避。

这个项目里的事务管理就是典型的自动配置结果。spring-boot-starter-jdbcspring-boot-starter-data-jpa被引入后,DataSourceTransactionManagerAutoConfiguration会自动注册事务管理器,代码里只需要在 Service 方法上加@Transactional,不需要写任何 XML 配置。这对毕业设计的好处是,老师检查代码时不会看到一大堆 Spring 样板配置,但这恰恰意味着你必须能解释自动配置的边界。

2.3 前后端分离结构里的 .bak 文件与 Maven Wrapper

项目文件里的index.html.bakupdate-password.vue.bakBreadCrumbs.vue.bak不是业务文件,而是开发者在 IDE 里手动备份的旧版本。.bak后缀不会参与前端构建,但放在源码仓库里会让人困惑,我一般会建议删掉并改用 Git 分支管理。不过保留它们有一个好处:能看出开发过程是先有登录和修改密码功能,再扩展了后台布局组件。

mvnw.cmd是 Maven Wrapper 的 Windows 执行脚本。它的作用是锁定 Maven 版本,避免“本机能跑,换台机器就报 Maven 版本不对”的问题。这个项目配套Maven 3.3.9,和 Spring Boot 1.5.x 到 2.x 早期版本都能兼容。如果你把 JDK 换成 17 以上,Maven 3.3.9 会因为无法识别 class 文件版本而失败,所以跑之前先确认一下JAVA_HOME指向的是 JDK1.8。

前端部分的IndexAsideStatic.vueIndexHeader.vueBreadCrumbs.vue是管理后台的布局组件,分别对应侧边栏、顶部导航和面包屑。很多基于 Spring Boot 的 Java 毕设前端都直接从开源后台模板改造,这类组件通常依赖 Vue2 和 ElementUI。如果前端npm install报错,先看 Node 版本是否超过 16,因为 Vue2 + node-sass 在 Node 高版本下编译会出现 Python 和 C++ 构建报错,这不是业务代码的问题。

提示:接手这种项目时,优先查看前端包管理文件里的依赖版本,不要盲目用npm update把所有依赖升级一遍,否则组件 API 不兼容会牵出一堆无头异常。

3. 从 JDK1.8 到 MySQL5.7:本地环境与启动参数

3.1 用 IDEA 创建 Spring Boot 项目时的版本匹配

很多人在 IDEA 里新建 Spring Boot 项目时习惯选最新版本,但这套二手交易平台用的技术栈比较老,如果你在 2025 年新建项目并选择 Spring Boot 3.x,JDK 必须是 17,和摘要里的 JDK1.8 就完全错开了。所以更稳妥的做法是:直接打开源码目录里的pom.xml,看<parent>标签里锁定的<version>,不要自行升级。

Spring Boot 版本与外部容器的兼容性有个大致表格:

Spring Boot 版本默认内嵌 Tomcat可兼容的外部 TomcatJDK 要求
1.5.x8.5.xTomcat 7/81.7+
2.1.x9.0.xTomcat 8.5+1.8+
2.7.x9.0.xTomcat 9+1.8+
3.0.x 及以上10.1.xTomcat 10+17+

这个项目如果坚持用 Tomcat7 部署 war 包,Spring Boot 版本最好控制在 1.5.22。如果项目已经用了 Spring Boot 2.x,那就走java -jar内嵌容器运行,不要去改packaging为 war。很多毕业生踩的坑是:学校服务器只有 Tomcat7,于是把 Spring Boot 2.x 打成 war,结果启动时NoSuchMethodError满屏飞,最后答辩前夜才降版本。我一般会建议先在本地确认启动方式再动代码。

3.2 数据库初始化与 application.yml 参数

MySQL 5.7 建库时要注意字符集和排序规则。直接用CREATE DATABASE second_hand DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;比在 IDE 里右键新建要更可控,因为utf8mb4能存 emoji 和部分生僻字。导入数据库脚本时,Navicat11 与 MySQL 5.7 的连接要选“使用旧版密码认证”吗?其实不需要,MySQL5.7 默认的mysql_native_password协议 Navicat11 完全支持。

application.yml是后端项目的核心配置文件,建议至少包含下面这一段:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/second_hand?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true

参数说明:useUnicode=true&characterEncoding=utf8保证中文不会乱码;serverTimezone=Asia/Shanghai是 JDBC 驱动版本升级后必须加的时区参数,不加会报The server time zone value错误;max-file-size控制上传图片大小,二手交易的商品图一般不超过 5MB,给到 10MB 是防止用户传过大的相机原图。map-underscore-to-camel-case开启后,数据库字段seller_id能自动映射到 Java 属性sellerId,省掉一长串 resultMap。

3.3 一键安装与运行脚本的完整解读

项目根目录下的1-install.bat2-run.bat是给自己或答辩老师准备的快捷启动方式。两个脚本内容大致可以补全成下面这样:

@echo off set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_202 set MAVEN_HOME=D:\apache-maven-3.3.9 call mvnw.cmd clean install -DskipTests pause

1-install.bat的逻辑是先设置 JDK 环境,再用mvnw.cmd执行打包。clean会删除 target 目录里的旧产物,install会把构建结果安装到本地 Maven 仓库,-DskipTests跳过测试用例,但不会跳过测试代码编译。如果你想跳过编译测试类,速度更快,可以改成-Dmaven.test.skip=true。这个区别如果答得出来,老师会觉得你确实手动构建过。

@echo off set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_202 java -jar target\second-hand-0.0.1-SNAPSHOT.jar --server.port=8080 pause

2-run.bat直接用java -jar启动内嵌容器。--server.port=8080是启动参数,优先级高于application.yml里的配置。如果你本机 8080 被占用了,可以把脚本里的端口改为 8081,不需要改代码重新打包。启动后看到Started Application in x.xxx seconds说明后端正常,这时候再去启动前端项目才能登录。

前端如果也要一键启动,一般会有package.json文件,运行npm install安装依赖,然后npm run dev启动 Vue 开发服务器。Vue CLI 默认端口是 8080,和后端冲突,所以前端开发服务器通常会配成 9528 或 8081,并在vue.config.js里配置/api代理到后端 8080。见别人的项目时先找代理配置,能找到说明作者真的联调过前后端。

4. 交易核心链路:登录、发布商品与订单状态机

4.1 登录会话与拦截器实现

二手交易平台有两种登录设计:基于 Session 的传统方式和基于 JWT 的无状态方式。后端项目如果做了前后端分离,JWT 更常见,因为不需要考虑 Session 跨域共享问题。这里给出一个最小可用的拦截器实现:

@Component public class JwtInterceptor implements HandlerInterceptor { private static final String SECRET = "your-256-bit-secret"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; } String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { String jwt = token.substring(7); try { Claims claims = Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(jwt) .getBody(); request.setAttribute("userId", claims.get("userId")); return true; } catch (Exception e) { // 校验失败 } } response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } }

代码的逻辑是:请求进入后先放行 OPTIONS 预检请求,然后再从请求头取Authorization,截掉Bearer这段前缀得到 JWT 本体,用密钥解析。解析失败说明 token 过期或被人篡改,直接返回 401。request.setAttribute是为了把当前登录用户的 id 透传给后续 Controller,避免在业务代码里重复解析 token。

这个拦截器要生效还需要注册到 WebMvc 配置里:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new JwtInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/api/auth/login", "/api/goods/list"); } }

addPathPatterns("/api/**")拦截所有以/api/开头的接口,登录和商品列表是游客也能访问的,所以要排除。注意excludePathPatterns里最好把前端轮播图、公告这类公开接口也放进去,否则首页打开会黑屏。

4.2 商品发布中的文件上传与图片回显

商品发布页面通常需要一个富文本编辑器和一个图片上传按钮。后端的 Controller 接收图片后要把图片存到本地磁盘,并在数据库里保存可访问的 URL。下面是一个常见的上传接口:

@PostMapping("/api/goods/upload") public Result upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) { String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String fileName = UUID.randomUUID().toString().replace("-", "") + ext; String savePath = "D:/upload/" + fileName; File dest = new File(savePath); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } file.transferTo(dest); String url = "/upload/" + fileName; return Result.success(url); }

逻辑说明:先取出原始文件名并截取扩展名,防止用户上传.jsp.exe这类危险文件;再用 UUID 拼出新文件名,避免重名覆盖。transferTo是 Spring MVC 提供的快捷方法,底层通过流拷贝完成上传。这里我把保存路径固定为D:/upload/,实际项目中一般放到服务器专用目录。

但图片上传后,浏览器要能通过 URL 访问到磁盘文件,必须配置静态资源映射:

@Configuration public class ResourceConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:D:/upload/"); } }

addResourceHandler定义 URL 前缀,addResourceLocations指定磁盘目录。file:前缀是必须的,表示读取本地文件而不是 classpath。两个方法保证/upload/xxx.jpg能映射到D:/upload/xxx.jpg。这里有个细节:如果服务器是 Linux,路径要改成/home/admin/upload/,Windows 和 Linux 的目录结构差异会导致图片上传成功但回显失败。

4.3 订单状态机的枚举与事务控制

二手交易平台订单的状态不能简单用一个字符串存,因为业务状态是有前置条件的。推荐用枚举加状态机,先定义订单状态:

public enum OrderStatus { PENDING_PAYMENT(0, "待付款"), PENDING_DELIVERY(1, "待发货"), PENDING_RECEIPT(2, "待收货"), COMPLETED(3, "已完成"), CANCELED(4, "已取消"); }

然后在一个订单状态表中定义允许的转换路径:

当前状态允许转换到触发动作
待付款待发货 / 已取消用户支付 / 超时取消
待发货待收货 / 已取消卖家发货 / 买家申请退款
待收货已完成买家确认收货
已取消终态
已完成终态

为什么需要这张表?因为如果代码里到处写if ("1".equals(order.getStatus())),两个开发各自加状态时会覆盖对方的判断。用状态机枚举后,可以这样校验:

public void confirmReceive(Integer orderId, Integer userId) { Order order = orderMapper.selectById(orderId); if (order == null || !order.getBuyerId().equals(userId)) { throw new BusinessException("订单不存在"); } if (order.getStatus() != OrderStatus.PENDING_RECEIPT.getCode()) { throw new BusinessException("当前状态不能确认收货"); } order.setStatus(OrderStatus.COMPLETED.getCode()); orderMapper.updateById(order); }

这段代码里,先校验订单归属,再校验状态是否等于待收货,最后才更新为已完成。状态判断用==还是equals取决于枚举的写法,这里取的是枚举中的 int 值,所以用!=做比较没有问题。整个操作要加@Transactional,因为确认收货后一般还要更新卖家账户余额、生成或修改交易记录,这些操作必须打包成一个事务。

事务控制的常见误区是只在 Mapper 层加@Transactional。正确做法是加在 Service 实现类上,且要避免在一个类内部通过this调用带事务的方法,否则 Spring AOP 代理不生效。这一点在答辩时经常被追问,属于“看着简单但现场容易翻车”的知识点。

5. 答辩高频追问:heapdump 泄露、定时下架与本地验证

5.1 Actuator heapdump 敏感信息泄露修复

毕业设计如果引入了spring-boot-starter-actuator,默认不会暴露heapdump,但很多人为了看监控信息会写成exposure.include: "*",这一下就把heapdumpshutdown端点暴露了。攻击者访问http://localhost:8080/actuator/heapdump下载内存快照,用 MAT 或 Eclipse Memory Analyzer 打开,就能从堆里提取到数据库密码和 JWT 的 secret。这个漏洞在 springboot 面试题里也是高频考点。

建议把配置收敛成:

management: endpoints: web: exposure: include: health,info endpoint: health: show-details: never

这样生产环境只开放healthinfohealth探活,info可以放项目版本、作者信息,其他监控端点一律不暴露。如果确实需要查看内存信息,可以研究 JDK 自带的jmap命令,它在宕机或性能排查时比 heapdump 端点更安全,因为不经过 HTTP 网络。

5.2 商品到期自动下架的 @Scheduled 实现

二手交易和商城不一样,商品有明确的展示结束时间。定时任务最常见的实现是 Spring 自带的@Scheduled。启动类上加@EnableScheduling开启任务调度,然后在任务类中写一个方法:

@Component public class GoodsExpireTask { @Scheduled(cron = "0 30 2 * * ?") public void autoOffShelf() { goodsService.autoOffShelf(); } }

cron 表达式0 30 2 * * ?表示每天凌晨 2 点 30 分执行一次。为什么选这个时间?因为二手交易平台白天的操作量大,凌晨触发可以避开数据库高峰,也符合“商品到期当天自动下架”的业务预期。对应的 SQL 是:

UPDATE goods SET status = 0 WHERE status = 1 AND end_time < NOW();

这里的status=1表示上架中,status=0表示下架。直接一条 SQL 全表更新在小项目里没有问题,但如果你想把下架记录写入日志表,最好在 Java 里查出过期商品 id,循环处理并记录日志,否则排错时没有依据。定时任务的缺点是不可控,如果服务器在凌晨 2 点关机,任务永远不会执行,所以偶尔也要看一眼日志确认执行时间。

5.3 用一条命令验证整套交易流程

答辩前最怕的不是功能缺失,而是整套流程演示到一半崩了。建议提前用 curl 做一次接口级验证,而不是只靠浏览器点击。启动后端后,先请求登录接口:

curl -X POST http://localhost:8080/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}'

这段命令会返回一个 JSON,里面包含 JWT token。拿这个 token 去请求商品列表接口:

curl -H "Authorization: Bearer <token>" \ "http://localhost:8080/api/goods?page=1&limit=10"

如果返回商品 JSON 数组,说明数据库连接、Mapper 映射、拦截器逻辑都正常。此时再打开浏览器进入管理后台,基本不会出现白屏或列表加载失败。如果登录接口报 401,优先检查excludePathPatterns是否把登录接口排除;如果商品列表为空,检查数据库goods表里有没有初始化数据。

整个项目最值得优化的地方不是加更多功能,而是把启动流程收敛成两条命令:一条安装依赖,一条启动服务。你把这套逻辑讲清楚,老师看到的就不是“复制来的源码”,而是一个能从环境到数据落地的完整工程。

本文还有配套的精品资源,点击获取

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

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

立即咨询