Spring Boot+Vue二手交易系统毕设实战:从数据库到答辩全攻略

发布时间:2026/9/30 15:00:28
Spring Boot+Vue二手交易系统毕设实战:从数据库到答辩全攻略 又到了毕业设计的季节。如果你在网上反复搜过“springboot vue 二手物品交易 boot 代码”大概率是选题选到了这个方向或者正被导师一句“做一个系统吧”架上了梁山。二手交易平台确实是被选得最多的毕设方向之一原因很现实业务大家都懂功能边界清晰技术栈又正好踩在主流上。用 Spring Boot 写后端、Vue 写前端、MySQL 存数据既能避开冷门技术带来的环境灾难又能把软件工程里需求分析、系统设计、开发实现、测试验收这一整套流程完整走一遍。这篇文章是我带过好几个类似项目之后整理的经验内容包括选题逻辑、数据库设计、核心功能代码怎么写、论文怎么组织、答辩怎么避坑适合正在赶进度、或者想把这个项目真正跑明白的人。所谓“boot 代码”说到底就是一份能构建、能运行、能演示的 Spring Boot 工程但关键不在于你能搜到多少代码而在于每一块功能你能不能讲清楚、改得动。1. 项目定位与选题逻辑为什么是二手交易平台 Spring Boot1.1 二手交易需求与毕设的匹配点先聊选题。很多人看到“二手交易系统”会觉得太普通没有亮点。但毕设和商业项目不一样毕设考察的是你能否独立完成一个完整的信息系统。二手交易平台在业务复杂度上非常适中有用户、商品、订单几个核心实体有登录、发布、检索、下单几条基本链路既不会简单到只有一张表增删改查也不会复杂到牵扯支付、物流、分布式事务这些根本讲不透的东西。更重要的是这套业务和生活场景贴得很近。答辩的时候老师问“你为什么要做这个系统”你可以非常自然地回答“校园里二手书籍、二手数码产品交易需求大目前缺少一个专注该场景的平台”。这个说法既真实又不需要编造什么高大上的行业痛点。系统的可扩展性也强后续想加个评论、加个管理员统计报表、甚至对接微信支付都有明确的位置可以插进去这正好对应论文里的“系统展望与改进”。1.2 技术组合的取舍Spring Boot Vue 到底好在哪Spring Boot 在这个题目里的优势是“省心”。传统的 SSM 项目要写一堆 XML 配置配置数据源、配置事务、配置扫描包光折腾环境就能消耗一个礼拜。Spring Boot 用自动装配把大部分配置直接约定了内置 Tomcat写一个带RestController的类就能提供接口这对毕设节奏来说简直是救命。Vue 这边则是“好写界面”。用 Vue 组件可以把导航栏、商品卡片、订单状态标签拆成独立组件页面之间通过 Vue Router 跳转数据通过 Axios 请求后端接口。前后端分离以后前端开发不依赖后端联调你可以在本地 mock 数据把页面全部写完最后再对接真实接口。这种开发方式在论文里也特别好讲画一张架构图把“浏览器 → Vue 前端 → 后端接口 → 数据库”的链路交代清楚工作量描述就非常醒目。技术栈选型上我的建议是后端 Spring Boot 2.7.x配合 MyBatis-Plus 做数据访问前端 Vue 2 Element UI或者 Vue 3 Element Plus按你自己熟悉的来。如果时间紧张选 Vue 2 生态最稳网上的资料也最多。数据库用 MySQL 5.7 或 8.0 都可以。这套组合在部署和演示时最不容易出幺蛾子。1.3 系统功能范围先圈定边界再谈实现毕设最容易犯的错误就是功能堆得太多。我见过有人把秒杀、聊天、推荐算法全塞进二手交易系统结果没一个模块做完整。正确做法是先圈定 MVP也就是最小可用功能集。用户模块注册、登录、退出、修改个人信息商品模块发布商品、编辑商品、下架/重新上架、商品列表、商品详情搜索与分类按关键词搜索、按分类筛选、按价格区间过滤收藏模块收藏/取消收藏、我的收藏列表交易模块买家发起购买、卖家处理订单、买卖双方确认完成个人中心我发布的、我买到的、我卖出的在此基础上如果进度有余力再考虑加管理员后台做用户管理和商品审核。功能清单越早定死后面的时间就越宽裕。论文里的“需求分析”章节也直接从这个功能清单展开每一块画个用例图就可以。2. 架构设计与数据库建模先把地基打结实2.1 前后端分离架构的关键约定二手交易系统的架构属于典型的单体前后端分离Vue 负责页面渲染和交互Spring Boot 只负责提供 JSON 接口。项目结构上分成前端secondhand-front和后端secondhand-server两个目录各自独立开发、独立启动。请求链路是浏览器 → Vue Router 匹配路由 → 页面组件 → Axios 发起 HTTP 请求 → Spring Boot Controller → Service → Mapper → MySQL然后数据原路返回。为了让前端统一处理返回结果后端接口要约定一个统一响应体我一般习惯用三个字段public class ResultT { private Integer code; // 200 成功500 失败 private String msg; // 提示信息 private T data; // 业务数据 }所有 Controller 的方法都返回Result前端 Axios 拦截响应后先判断code再决定是提示错误还是渲染数据。跨域问题也要提前处理开发时前端跑在 8080后端跑在 8081必须允许跨域。最简单的做法是在后端加一个全局 CORS 配置。候选代码经验设置Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }前后端分离还有个小细节接口前缀统一加/api比如/api/user/login、/api/goods/list。这样后端写 Controller 的时候路径更清晰将来部署到同一域名下也可以通过 Nginx 的/api规则做代理转发。2.2 数据库表设计五张核心表就够了二手交易系统的核心数据关系并不复杂核心表建议控制在五到六张千万不要一开始就把表拆得特别碎。我在项目里常用的表结构大概是这样的表名作用核心字段t_user用户表id, username, password, nickname, avatar, phone, create_timet_goods商品表id, user_id, category_id, title, description, price, cover_image, status, create_timet_favorite收藏表id, user_id, goods_id, create_timet_order订单表id, order_no, goods_id, buyer_id, seller_id, price, status, create_timet_category分类表id, name, sort商品表里的status字段很关键我给你一个约定0 代表上架中1 代表已下架2 代表已售出。买家搜索商品时SQL 条件里必须加上status 0否则下架和已售出的商品就会暴露出来。这个字段是整个商品模块的状态开关前后端都要对应好。订单表的status我一般这样设计0 代表已取消1 代表买家已下单待卖家处理2 代表卖家已同意3 代表买家确认收货完成。这里不需要做得太复杂不用引入“退货”“退款”“超时自动关闭”这些状态因为那是商业系统才需要考虑的流程。毕设只要能把一条订单从创建走到完成讲清楚就已经满足要求。分类表可能有人觉得没必要认为在商品表里直接存分类名称就行。但加了分类表以后商品列表的分类筛选就变成按category_id关联查询前端下拉框也从这张表动态加载性能和可维护性都更好。数据库设计在论文里是要重点写的一张 E-R 图多一张正式的关系表图会好看很多。2.3 设计决策背后的权衡JWT、MyBatis-Plus 和图片存储问到“为什么用 JWT 而不用 Session”这是答辩大概率会碰到的问题。我的理解是前后端分离环境下Session 依赖服务端保存状态如果将来有多个后端实例Session 同步是个麻烦JWT 把用户信息加密放到客户端后端只负责验证签名天然适合分布式场景。对毕设来说JWT 的实现也比 Session 配置更容易解释生成令牌、校验令牌、前端携带令牌一条链路非常清晰。数据访问层我推荐 MyBatis-Plus核心原因是单表 CRUD 不用自己写 SQL。继承一个BaseMapperT就有现成的selectById、selectList、insert、updateById。分页查询也有内置的Page对象。这对不熟悉 SQL 的同学非常友好而且它的LambdaQueryWrapper做条件查询极大减少了字符串拼接出错的可能。图片存储的问题更要提前想清楚。毕设阶段不要一上来就配置 OSS本地磁盘存储完全够用。后端接收上传的MultipartFile把文件写到项目根目录下的upload文件夹然后把相对路径存到数据库并配置静态资源映射让浏览器可以通过 URL 访问。这个方案零成本、无依赖、演示可靠唯一要做的就是启动时确认 upload 目录存在。3. 核心功能实现拆解从登录到成交的完整链路3.1 登录鉴权流程JWT 令牌的生成与拦截登录是每个系统的门面实现方式直接决定了后续接口能否正常工作。整套流程分五步用户提交用户名和密码后端查 t_user 表用 BCrypt 校验密码校验通过后生成 JWT 令牌把 userId 放进去前端把 token 存到 localStorage后续请求通过拦截器自动在请求头带上 token密码加密必须用加密算法不要明文存库。Spring Security 的BCryptPasswordEncoder是最省事的选择即使不和 Spring Security 集成单独拿来加密、校验也没问题。生成 JWT 的代码建议封成一个工具类方便所有接口使用public class JwtUtil { // 注意真正使用时要放到配置里不要直接写死在代码中 private static final String SECRET secondhand-app-secret; public static String createToken(Integer userId, String username) { return Jwts.builder() .claim(userId, userId) .claim(username, username) .setExpiration(new Date(System.currentTimeMillis() 7L * 24 * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }前端需要配置一个 Axios 拦截器让每次请求都自动携带 token// request.js import axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器把 token 放进 header service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config })后端写一个拦截器拦截除了登录、注册、商品列表以外的所有请求。如果请求头里没有 token 或者 token 解析失败直接返回 401。这个拦截器不要做得太复杂能判断放行和拦截即可。进入商品发布、下单这些操作型接口时从 token 里取出 userId作为当前用户 ID。这里有一点要提醒不要盲目相信前端传过来的 userId所有敏感操作必须以 token 里的 userId 为准否则别人改一下参数就能操作你的账号这类问题在答辩时容易被老师抓住。3.2 商品发布与图片上传弄清这几个坑就能跑通商品发布页面是前端工作量最大的部分。表单字段包括标题、分类、描述、价格、封面图、详情图。在 Element UI 里可以用 Form 组件加上图片上传组件完成。提交时把文本字段和图片链接一起组装成对象调用/api/goods/add接口。图片上传我建议做成独立接口前端先把图片传上去拿到返回的 URL再随商品信息一起提交。不要尝试把图片转成 Base64 直接存数据库那样数据表会非常臃肿接口响应也会很慢。后端接收图片的核心逻辑如下PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.error(请选择文件); } // 生成唯一文件名避免中文和重复名导致的问题 String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName System.currentTimeMillis() _ UUID.randomUUID().toString().replace(-, ) ext; File dir new File(upload); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir, fileName)); return Result.ok(/upload/ fileName); }生成唯一文件名这步很关键。如果直接用用户上传的原始文件名两个用户上传同名图片就会互相覆盖中文文件名还有乱码问题。加上时间戳和 UUID 组合可以基本保证唯一。文件大小也要限制我一般控制在单张 5MB 以内。文件保存路径要配套静态资源映射否则前端拿到了/upload/xxx.jpg也访问不到。在 Spring Boot 里添加一个 WebMvc 配置Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 将 /upload/** 路径映射到本地磁盘目录 registry.addResourceHandler(/upload/**) .addResourceLocations(file:upload/); } }注意file:upload/是相对路径启动项目时当前目录就是项目根目录所以图片会落在项目文件夹下的upload目录中。演示的时候不要移动这个目录否则图片就找不到了。更稳妥的做法是用绝对路径写成file:D:/work/secondhand/upload/但这样换电脑运行就不方便你看自己情况选择。3.3 商品列表与条件搜索模糊查询 动态条件拼接商品列表页要支持几个筛选项关键词、分类、价格区间、最新/价格排序。这类“多条件组合查询”是最适合体现基本功的地方不建议写死 SQL更不建议用字符串拼 SQL。MyBatis-Plus 的LambdaQueryWrapper可以优雅解决public PageGoods searchGoods(String keyword, Integer categoryId, Double minPrice, Double maxPrice, int pageNum, int pageSize) { PageGoods page new Page(pageNum, pageSize); LambdaQueryWrapperGoods wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(keyword), Goods::getTitle, keyword) .eq(categoryId ! null, Goods::getCategoryId, categoryId) .ge(minPrice ! null, Goods::getPrice, minPrice) .le(maxPrice ! null, Goods::getPrice, maxPrice) .eq(Goods::getStatus, 0) .orderByDesc(Goods::getCreateTime); return goodsMapper.selectPage(page, wrapper); }这里用到了条件构造器的一个特性第一个参数是布尔值只有为 true 时才拼接后面的条件。比如categoryId是 nulleq这行就会自动跳过不会漏出category_id null这种错误 SQL。这个写法在论文里可以重点讲一层通过条件构造器实现动态 SQL 的拼接既保证查询灵活又从框架层面规避了 SQL 注入风险。价格排序我单独说一句。orderByDesc(Goods::getPrice)是按价格降序如果想做“最新优先”就按create_time排序。前端列表的花费数量和排序方式最好通过参数传递不要把排序写死在 SQL 里。搜索的返回结果建议包裹成分页对象里面包含总记录数total、当前页数据records、当前页码current、总页数pages。前端用el-pagination组件直接对接数据格式完全兼容。3.4 订单状态机从“我想要”到“成交”的闭环交易模块是这个项目和普通 CRUD 系统拉开差距的地方。订单不是一个简单的增删改查它内部有状态流转需要认真设计。我的简化交易流程是买家在商品详情页点击“立即购买”后端先校验商品是否存在、是否处于上架状态、是不是本人发布的商品然后创建一条订单订单状态为“待卖家处理”。卖家在“我卖出的”列表里看到这条订单可以选择“同意”或“拒绝”。如果同意订单进入“待完成”买家确认收到商品后点击“确认完成”订单状态变为“已完成”。对应的状态值状态值含义触发动作1待卖家处理买家提交订单2卖家已同意卖家同意交易3已完成买家确认收货0已取消卖家拒绝或任一环节取消后端只需要写一个状态变更的接口用参数传递目标状态PostMapping(/order/updateStatus) public ResultVoid updateStatus(RequestParam Integer orderId, RequestParam Integer status, RequestParam Integer userId) { Order order orderMapper.selectById(orderId); // 判断操作者是不是订单买卖双方之一防止越权操作 if (!order.getBuyerId().equals(userId) !order.getSellerId().equals(userId)) { return Result.error(无权操作该订单); } order.setStatus(status); orderMapper.updateById(order); return Result.ok(); }表面上看只是更新了个数字但项目里真正要关注的是状态流转的合法性。比如订单已经“已完成”就不能再变回“待处理”。这里我建议给可以转换的状态做一个校验如果是卖家操作就把当前状态从 1 改成 2 或 0如果是买家操作就把当前状态从 2 改成 3。虽然毕设不要求写特别复杂的状态机但你要能在答辩时讲出“为什么要校验状态的流转顺序”这个问题。商品和订单之间有个一致性问题商品一旦被买走订单进入待处理商品的状态就不应该还是 0。所以买家下单时后端要同步把商品status改成 1下架这样其他买家再搜就搜不到了。如果卖家拒绝交易再把商品状态改回 0。这个联动关系在答辩里是很好的业务逻辑亮点。4. 论文写作、演示与答辩代码写好只是第一步4.1 论文结构怎么组织截图怎么留很多同学代码写完了论文却拖到最后一刻。其实论文结构和项目开发是同步进行的。二手交易系统这种题目论文的经典章节组织方式如下绪论背景、意义、国内外现状、论文结构关键技术介绍Spring Boot、Vue、MyBatis-Plus、MySQL系统分析可行性分析、需求分析、用例图、功能需求和非功能需求系统设计总体架构图、模块设计、数据库 E-R 图、数据表设计系统实现逐模块放截图和核心代码讲解系统测试功能测试用例表、部分性能或兼容性测试总结与展望我觉得最容易被忽略的是第 6 章测试。老师非常看重系统是否经过验证你不一定真的跑过完整的自动化测试但一定要有一张“测试用例表”列出功能点、操作步骤、预期结果、实际结果、是否通过。从开发第一天就要养成截图留档的习惯。每完成一个模块比如注册页、商品发布、订单列表都截图保存。写论文的时候系统实现章节就是把这些截图按模块顺序贴进去然后在每一张图下面写一两段说明讲讲这个页面调用了哪些接口、核心逻辑是什么、遇到了什么问题。这样论文系统实现章节至少能轻松写出一万字的初稿完全不用临时补图。4.2 演示环境准备与常见报错清单答辩演示是翻车事故的高发区最大的原因是环境不一致。我建议你在答辩前至少完整演示两遍第一遍把项目从零启动到能登录第二遍完整走一遍发商品、下单、成交的流程。这是对整个项目状态最有效的检验。常见报错我整理成一张速查表报错现象原因处理方法前端请求接口 404后端未启动或路径拼写不一致先在后端浏览器直接访问接口地址确认数据库连不上 Access deniedMySQL 用户名密码不对检查 application.yml 里的账号密码中文乱码数据库或连接配置没有指定 UTF-8建库时指定 utf8mb4URL 加 characterEncodingutf8图片加载不出来静态资源映射或路径错误试访问 /upload/文件名检查拦截器是否放行了该路径Node 启动报错node_modules 依赖缺失或版本不一致删除 node_modules 重新 npm install端口被占用本机 8080/8081 已被其他程序占用改端口或者杀掉占用进程一个很容易忽略的问题是数据库时区。如果你用的是 MySQL 8.0连不上会报时区错误需要在 JDBC URL 里增加serverTimezoneAsia/Shanghai。此外后端拦截器要记得把/upload/**这类静态资源路径放行否则图片请求也会被拦截。4.3 答辩高频问题快查表答辩时老师一般不会让你现场写代码但一定会通过提问来验证你对项目的理解。下面几个问题几乎是必问的为什么选择前后端分离架构回答要点前后端职责清晰前端关注页面交互后端关注业务逻辑开发时可并行便于多端扩展同一个后端接口可以服务 Web、小程序等部署时通过 Nginx 转发 API 请求即可。JWT 和 Session 有什么区别回答要点Session 存储在服务端需要维持会话状态JWT 是自包含令牌服务端无状态验证签名即可。在前后端分离场景下 JWT 更合适扩展性更好。你的项目有什么难点这是最容易答空的问题。你要说出一到两个具体点比如订单状态流转和商品状态的联动需要考虑并发场景下重复下单的问题图片上传要处理文件名冲突和静态资源映射。说自己真实做过的东西哪怕不复杂也比说“本项目很完善”有说服力得多。密码是怎么存储的回答要点使用 BCrypt 加盐哈希不能明文存储即使数据库泄露也无法直接得到原密码。系统如何防止 SQL 注入回答要点使用 MyBatis-Plus 条件构造器参数经过预编译处理而不是字符串拼接 SQL。框架层面已经做了防护。如果同一商品被两个买家同时下单怎么办回答要点可以在下单前再次检查商品状态并加上数据库乐观锁或悲观锁毕设场景下可以先用 SQL 的update t_goods set status 1 where id ? and status 0这种条件更新保证只有一个请求成功。这个问题能把你的思考深度展示出来。最后分享一点实际体会带过不少同学做这类项目我最深的感觉是二手交易平台这种毕设技术上并不难难的是“能不能把整个项目讲成一个完整自洽的故事”。故事的主线就是一条买家发起购买并最终成交的交易链路所有模块都围绕这条主线展开数据库表按这条链路去设计论文的章节按这条链路去安排。只要主线不散你的系统就不会被评价为“像拼接出来的”。建议你完成后把代码整理到 GitHub 或 GiteeREADME 写清楚运行步骤这既是给老师的交付物也是给自己的一份记录。论文里的“系统测试”尽量用真实测试数据测试用例表格列清楚最后把演示流程在答辩前完整跑通一遍操作时慢一点边操作边解释每一步做了什么。做到这些这个题目拿一个不错的成绩基本就稳了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询