二手交易平台毕业设计实战:基于 Spring Boot + Vue 的全栈架构解析与避坑指南
写论文前先写代码,这句话在二手交易平台毕设里尤其真实。去年我带 12 组同学做同款题目,9 组在“登录状态丢失”“图片显示 404”“并发超卖”三连击下连夜改需求。痛定思痛,把最省头发的一套实现整理出来,既当课程设计模板,也当踩坑笔记。下面按“背景→选型→实战→调优→展望”顺序展开,代码可直接拷贝到 IDEA / VSCode 跑通。
1. 学生项目常见“四连坑”
- 架构随意:Controller 里写 SQL,业务逻辑与数据库耦合,一改需求全文件爆红。
- 鉴权裸奔:
session.setAttribute("user", uid)一把梭,集群部署直接掉线。 - 文件乱放:图片存工程目录,打包 jar 后磁盘满,Git 仓库体积直奔 1G。
- 并发忽略:商品库存用
int stock字段,高并发下单测试秒变负数,演示现场翻车。
2. 技术选型:为什么不是 Flask / Django / React?
| 维度 | Spring Boot + Vue | Flask / Django + React |
|---|---|---|
| 学习曲线 | Java 课已学,注解式配置零 XML;Vue 模板语法贴近 HTML | 动态语言+Hook 语法,对只写过 C/Java 的本科生门槛高 |
| 脚手架生态 | Spring Initializr 一键骨架;Vue CLI 4 自带热更新 | Django 前后端模板混合,React 需自己配 Webpack |
| 教学资源 | 慕课网、B 站中文视频管够 | 优质资料多为英文,毕设周期内吃透不易 |
| 就业加成 | 国内 Java 岗占 60%+,Vue 在中小企业普及 | 小众组合,校招面试被问“为什么不用 Spring” |
一句话:用最熟悉的栈,把精力留给业务而不是配环境。
3. 核心实现拆解
3.1 领域建模(DDD 轻量版)
- 用户(User)聚合:注册、登录、信誉分
- 商品(Item)聚合:标题、描述、价格、库存、状态机
- 订单(Order)聚合:买家、卖家、快照、状态流转
- 通用值对象:Money、ImageUrl、StatusEnum
先画类图,再写代码,比直接建表少返工 30%。
3.2 RESTful API 设计
| 资源 | 方法 | 路径 | 语义 |
|---|---|---|---|
| 用户 | POST | /api/users | 注册 |
| 会话 | POST | /api/sessions | 登录 |
| 商品 | GET | /api/items?status=SELLING | 在售列表 |
| 订单 | POST | /api/orders | 创建订单(含库存扣减) |
统一返回体:
{ "code": 0, "msg": "success", "data": {} }3.3 用户认证:JWT + 刷新令牌
- 登录成功后颁发
accessToken(15min)+refreshToken(7d) - 前端 Axios 拦截器发现 401,自动用
refreshToken换新的accessToken - 后端使用
spring-security-jwt过滤器,密钥存配置文件,部署时走环境变量
关键代码(Kotlin 版,Java 同理):
class JwtFilter : OncePerRequestFilter() { override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) { val token = request.getHeader("Authorization")?.removePrefix("Bearer ") ") if (!token.isNullOrBlank() && jwtUtil.validate(token)) { val auth = UsernamePasswordAuthenticationToken( jwtUtil.getUserId(token), null, emptyList()) SecurityContextHolder.getContext().authentication = auth } chain.doFilter(request, response) } }3.4 商品发布 & 图片上传
- 采用 OSS / MinIO 对象存储,前端直传,避免后台做流量中转
- 数据库存储“图片 URL 数组”字段,用 JSON 字符串,查询后直接返给前端
- 新增商品接口幂等:利用“用户 ID + 商品标题 MD5”做唯一索引,防止表单重复提交
@PostMapping("/api/items") public IdResp publish(@Valid @RequestBody PublishCmd cmd, @AuthenticationPrincipal Long userId) { String key = "pub:" + userId + ":" + Md5Util.hash(cmd.getTitle()); if (Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, "1", Duration.ofMinutes(1)))) { return IdResp.of(itemService.publish(userId, cmd)); } throw new BizException("操作太快,请稍后再试"); }3.5 交易状态机
订单状态:CREATED → PAID → DELIVERED → CONFIRMED → CLOSED
使用 Spring StateMachine 或枚举+策略模式均可,毕设阶段推荐后者,代码量小:
interface OrderStateHandler { OrderStatus next(Order order, OrderEvent event); } @Service class CreatedStateHandler implements OrderStateHandler { public OrderStatus next(Order order, OrderEvent event) { if (event == OrderEvent.PAY) return OrderStatus.PAID; throw new IllegalArgumentException("事件不支持"); } }4. 关键代码片段速查
4.1 后端:跨域 + 拦截器一站式配置
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(Cors走起Registry站位)) { registry.addMapping("/api/**") .allowedOriginPatterns("*") .allowedMethods("*") .allowCredentials(true); } }生产环境把
allowedOriginPatterns换成确切域名,避免 * 被安全团队打回。
4.2 前端:Axios 封装 & 自动刷新
// utils/request.js import axios from 'axios' const service = axios.create({ baseURL: '/api', timeout: 8000 }) // 响应拦截 service.interceptors.response.use( res => res.data, async err => { if (err.response?.status === 401) { await refreshToken() return service.request(err.config) // 重试原请求 } return Promise.reject(err) } ) export default service4.3 防重复提交:指令级节流
<template> <button @click="handleSubmit" v-throttle="2000">发布</button> </template> <script> Vue.directive('throttle', (el, binding) => { let lock = false el.addEventListener('click', () => { if (lock) return lock = true setTimeout(() => lock = false, binding.value || 1000) }) }) </script>5. 性能与安全:把 0.5 折提到 3 折
- SQL 注入:全项目用 JPA / MyBatis-Plus,禁止拼接
#{}以外的字符串 - XSS 过滤:Vue 本身转义双括号,富文本用
xss库白名单过滤 - 接口幂等:除“发布”外,下单、支付均带
clientToken,后端用唯一索引兜底 - 并发超卖:
UPDATE item SET stock = stock - 1 WHERE id = ? AND stock > 0+ 乐观锁版本号 - 数据库连接池:HikariCP 默认 10 并发压测就挂,毕设演示前把
maximum-pool-size调到 30,连接超时 5s
6. 生产环境部署避坑
- 跨域再提一次:Chrome 84+ 后
allowedCredentials=true时不能allowedOrigins="*",否则直接报错 - 静态资源路径:Vue 打包后
index.html里/js/app.xxx.js以/开头,若部署在二级目录需设publicPath: './' - 图片回源:OSS 开 CDN 回源,把
/img/**流量全部走 CDN,避免带宽账单爆炸 - 日志切割:Spring Boot 用
logback-spring.xml按天滚动,防止 1 个文件撑爆磁盘 - 廉价服务器 1C2G:数据库与 jar 同机跑,记得加 2G SWAP,否则 GC 时直接把 MySQL 挤掉
7. 效果展示
下图是卖家后台截图:状态标签、库存、操作按钮全部组件化,改颜色只改一行 CSS 变量。
8. 展望:下一步还能怎么卷?
- 消息通知:引入 WebSocket + Redis Pub/Sub,买家付款后实时推“订单待发货”
- Redis 缓存:热门商品列表缓存 30s,QPS 从 600 → 4000,期末答辩直接吹“高并发”
- 搜索引擎:Elasticsearch 做分词,标题+描述联合搜索,告别
LIKE '%键盘%'全表扫描 - 移动端:用 UniApp 直接复用 Vue 组件,两周就能出安卓 APK
我把完整代码扔到 GitHub,搜“second-hand-trading-boot-vue”就能 fork。毕设不是终点,把消息、缓存、搜索任选一个模块做完,面试时就能从“学生”秒变“能上线”的开发者。祝你答辩顺利,代码不挂,Star 不迷路。