校园二手交易系统实战开发全记录(SpringBoot+Vue+MySQL)
github地址:点击此处进入项目
去年,我做了一个校园二手交易系统,今天给大家,分享一下项目细节
文章目录
- 校园二手交易系统实战开发全记录(SpringBoot+Vue+MySQL)
-
- 一、项目概述与技术栈选型
-
- [1. 项目核心需求](#1. 项目核心需求)
- [2. 技术栈选型](#2. 技术栈选型)
- 二、核心模块开发与实战踩坑
-
- (一)后端核心开发与坑点解决
-
- [坑1:`@RequestBody`参数解析失败(400 Bad Request)](#坑1:
@RequestBody参数解析失败(400 Bad Request)) - [坑2:数据库字段映射错误(Unknown column 'xxx')](#坑2:数据库字段映射错误(Unknown column 'xxx'))
- 坑3:数据库表名冲突(MySQL关键字)
- 坑4:用户名重复注册(唯一索引冲突)
- [坑1:`@RequestBody`参数解析失败(400 Bad Request)](#坑1:
- (二)前端核心开发与坑点解决
-
- 坑1:按钮点击无响应(submit事件失效)
- 坑2:提示文案矛盾("发布失败:发布成功")
- [坑3:手机号长度超限(Data truncation错误)](#坑3:手机号长度超限(Data truncation错误))
- 三、全链路功能验证
-
- [1. 验证步骤](#1. 验证步骤)
- [2. 数据库验证结果](#2. 数据库验证结果)
- 四、项目优化方向(面试加分项)
-
- [1. 基础优化(快速落地)](#1. 基础优化(快速落地))
- [2. 功能扩展(提升项目价值)](#2. 功能扩展(提升项目价值))
- 五、面试高频问题与解答
- 六、总结
一、项目概述与技术栈选型
1. 项目核心需求
本项目实现极简版二手交易系统,聚焦用户注册、商品发布、订单创建三大核心模块,完成从前端页面交互到后端数据持久化的全链路闭环,具体需求如下:
- 用户模块:支持用户注册,实现密码加密存储,保障用户信息安全;
- 商品模块:支持商品发布,维护商品"在售/已售出"状态,关联卖家信息;
- 订单模块:支持订单创建,自动关联商品、买家、卖家信息,创建订单时同步更新商品状态。
2. 技术栈选型
技术栈选取主流、易用、面试高频的组合,兼顾开发效率与学习价值:
| 技术分层 | 技术选型 | 选型理由 |
|---|---|---|
| 后端框架 | SpringBoot + MyBatis-Plus | SpringBoot快速搭建后端项目,简化配置;MyBatis-Plus高效实现CRUD操作,减少手写SQL工作量 |
| 前端框架 | Vue2 + ElementUI | Vue2生态成熟、上手成本低;ElementUI提供丰富组件,快速搭建美观实用的前端页面 |
| 数据库 | MySQL | 关系型数据库,完美适配用户、商品、订单的关联数据存储场景 |
| 其他工具 | Axios | 前端发送HTTP请求,实现前后端数据交互;Lombok简化后端实体类代码 |
二、核心模块开发与实战踩坑
本部分聚焦开发过程中遇到的典型问题、原因分析、解决方案及核心代码,直击新手开发痛点。
(一)后端核心开发与坑点解决
坑1:@RequestBody参数解析失败(400 Bad Request)
-
现象:前端调用用户注册接口,后端返回400错误,控制台提示"Required request body is missing"。
-
原因 :前端请求未设置
Content-Type: application/json,导致后端无法识别JSON格式的请求体;或前端参数传递方式错误(如用URL拼接而非JSON请求体)。 -
解决方案
- 前端配置Axios请求头,指定数据格式为JSON;
- 后端接口使用
@RequestBody注解接收参数,确保前后端参数名一致。
-
核心代码
后端用户注册接口(UserController.java):java@RestController @RequestMapping("/api/user") @CrossOrigin // 解决跨域问题 public class UserController { @Autowired private UserService userService; @PostMapping("/register") public Result<?> register(@RequestBody User user) { boolean success = userService.register(user); return success ? Result.success("注册成功") : Result.error("注册失败"); } }前端Axios全局配置(main.js):
javascriptimport axios from 'axios'; Vue.prototype.$axios = axios.create({ baseURL: 'http://localhost:8080/api', timeout: 5000, headers: { 'Content-Type': 'application/json;charset=utf-8' } });
坑2:数据库字段映射错误(Unknown column 'xxx')
-
现象:发布商品接口调用时,后端报500错误,提示"Unknown column 'image_url' in 'field list'"。
-
原因 :后端实体类定义了
imageUrl字段(驼峰命名),但数据库goods表未创建对应的image_url字段,MyBatis-Plus默认开启驼峰命名转下划线,导致字段映射失败。 -
解决方案 :建表时补全所有实体类对应的字段,确保实体类驼峰字段 ↔ 数据库下划线字段一一对应。
-
核心代码 :
goods表创建SQLsqlCREATE TABLE `goods` ( `id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '商品ID', `title` VARCHAR(255) NOT NULL COMMENT '商品标题', `price` DECIMAL(10,2) NOT NULL COMMENT '商品价格', `description` TEXT COMMENT '商品描述', `image_url` VARCHAR(255) COMMENT '商品图片URL', `user_id` BIGINT NOT NULL COMMENT '卖家ID', `status` TINYINT DEFAULT 0 COMMENT '0-在售 1-已售出', `create_time` DATETIME DEFAULT NOW() COMMENT '创建时间' ) COMMENT '商品表';
坑3:数据库表名冲突(MySQL关键字)
-
现象:创建订单接口报错,SQL执行提示"near 'order ( goods_id, user_id...' at line 1"。
-
原因 :订单表名定义为
order,而order是MySQL关键字(用于排序操作),直接使用会导致SQL语法错误。 -
解决方案 :表名添加前缀(行业通用规范),将
order表改为tb_order,同时修改实体类@TableName注解指定表名。 -
核心代码 :订单实体类(Order.java)
javaimport com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @Data @TableName("tb_order") // 指定数据库表名 public class Order { @TableId(type = IdType.AUTO) private Long id; private Long goodsId; // 关联商品ID private Long userId; // 买家ID private Long sellerId; // 卖家ID private BigDecimal price; // 订单价格 private Integer status; // 0-待付款 }
坑4:用户名重复注册(唯一索引冲突)
-
现象:重复注册同一用户名,后端报500错误,提示"Duplicate entry 'xxx' for key 'uk_username'"。
-
原因 :数据库
user表的username字段设置了唯一索引,保证用户名唯一性,但后端代码未提前校验,直接插入导致触发数据库约束报错。 -
解决方案:后端注册逻辑中,先查询用户名是否存在,存在则直接返回失败,避免触发数据库异常。
-
核心代码 :用户注册业务逻辑(UserServiceImpl.java)
java@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Override public boolean register(User user) { // 1. 用户名非空校验 String username = user.getUsername().trim(); if (username.isEmpty()) { return false; } // 2. 校验用户名是否已存在 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("username", username); if (this.count(queryWrapper) > 0) { return false; } // 3. 密码MD5加密 user.setPassword(PasswordUtil.encrypt(user.getPassword().trim())); // 4. 保存用户 return this.save(user); } }
(二)前端核心开发与坑点解决
坑1:按钮点击无响应(submit事件失效)
-
现象:点击"发布商品""创建订单"按钮,前端无任何反应,浏览器Network面板无请求记录。
-
原因 :使用ElementUI的
el-form和el-button,直接绑定submit事件时,el-form是封装组件,并非原生form标签,导致事件触发失效。 -
解决方案 :将按钮触发方式改为
@click,直接调用对应方法,避开组件事件兼容问题。 -
核心代码 :前端发布商品按钮(App.vue)
html<el-tab-pane label="发布商品" name="publishGoods"> <el-form :model="goodsForm" label-width="80px"> <el-form-item label="商品标题"> <el-input v-model="goodsForm.title"></el-input> </el-form-item> <el-form-item label="商品价格"> <el-input v-model="goodsForm.price" type="number"></el-input> </el-form-item> <el-form-item label="卖家ID"> <el-input v-model="goodsForm.userId" type="number"></el-input> </el-form-item> <!-- 关键:用@click触发方法 --> <el-form-item> <el-button type="primary" @click="handlePublishGoods">发布商品</el-button> </el-form-item> </el-form> </el-tab-pane>
坑2:提示文案矛盾("发布失败:发布成功")
-
现象:操作成功后,前端弹出错误提示"发布失败:发布成功",逻辑与提示完全矛盾。
-
原因 :前端响应判断逻辑错误,将后端返回的
code=200(成功状态)误判为失败,同时错误拼接了成功的响应信息。 -
解决方案 :统一响应判断标准------
code=200时弹出成功提示,否则弹出失败提示,明确区分msg(通用信息)和data(业务信息)。 -
核心代码 :前端发布商品方法(App.vue)
javascriptasync handlePublishGoods() { // 非空校验 if (!this.goodsForm.title || this.goodsForm.price <= 0 || this.goodsForm.userId <= 0) { this.$message.error("标题、价格、卖家ID不能为空!"); return; } try { const res = await this.$axios.post("/goods/publish", this.goodsForm); // 正确判断逻辑 if (res.data.code === 200) { this.$message.success("发布商品成功!"); this.goodsForm = { title: "", price: 0, description: "", userId: 0 }; } else { this.$message.error("发布失败:" + res.data.msg); } } catch (err) { this.$message.error("发布出错:" + (err.response?.data?.msg || err.message)); } }
坑3:手机号长度超限(Data truncation错误)
-
现象:注册时输入过长手机号,后端报500错误,提示"Data too long for column 'phone'"。
-
原因 :数据库
phone字段长度设置为VARCHAR(11),但前端未做长度校验,输入超过11位的手机号导致数据插入失败。 -
解决方案:前端添加手机号格式校验,限制输入为11位纯数字,从源头拦截非法数据。
-
核心代码 :前端手机号校验逻辑
javascriptasync handleRegister() { // 手机号格式校验 if (this.registerForm.phone && !/^1[3-9]\d{9}$/.test(this.registerForm.phone)) { this.$message.error("请输入11位有效手机号!"); return; } // 后续注册逻辑... }
三、全链路功能验证
1. 验证步骤
- 用户注册 :前端填写用户名、密码、手机号,点击注册 → 后端加密密码并保存 → 数据库
user表新增记录; - 商品发布 :填写商品标题、价格、卖家ID,点击发布 → 数据库
goods表新增记录,状态为0(在售); - 订单创建 :填写商品ID、买家ID,点击创建 → 数据库
tb_order表新增记录,同时goods表对应商品状态更新为1(已售出)。
2. 数据库验证结果
-
user表(部分数据)
sql+----+----------+----------------------------------+-------------+ | id | username | password | phone | +----+----------+----------------------------------+-------------+ | 5 | test9999 | e10adc3949ba59abbe56e057f20f883e | 13800138000 | +----+----------+----------------------------------+-------------+ -
goods表(部分数据)
sql+----+-------+---------+-------------+---------+--------+ | id | title | price | description | user_id | status | +----+-------+---------+-------------+---------+--------+ | 2 | myObj | 20.00 | test desc | 1001 | 1 | +----+-------+---------+-------------+---------+--------+ -
tb_order表(部分数据)
sql+----+----------+---------+-----------+-------+--------+ | id | goods_id | user_id | seller_id | price | status | +----+----------+---------+-----------+-------+--------+ | 2 | 2 | 1 | 1001 | 20.00 | 0 | +----+----------+---------+-----------+-------+--------+
四、项目优化方向(面试加分项)
1. 基础优化(快速落地)
- 接口参数校验 :引入JSR303注解(
@NotBlank、@NotNull、@Pattern),在实体类中添加校验规则,替代手动if判断; - 全局异常处理 :自定义
GlobalExceptionHandler,统一捕获SQL异常、参数异常等,返回标准化错误信息; - 分页功能实现 :基于MyBatis-Plus的
IPage实现商品列表分页查询,提升前端展示体验。
2. 功能扩展(提升项目价值)
- 商品图片上传:对接阿里云OSS,实现图片上传、存储、回显功能,完善商品信息;
- 订单状态管理:新增"付款""取消订单"接口,支持订单状态流转(待付款→已付款→已完成);
- 个人中心模块:开发"我的发布""我的订单"查询接口,前端实现对应页面,完善用户操作闭环。
五、面试高频问题与解答
-
问 :项目中如何保证数据一致性?比如创建订单时商品状态不会被重复修改?
答 :当前通过业务逻辑校验 保证------创建订单前先查询商品状态,仅当状态为"在售"时才执行创建逻辑并更新状态;若后续面临高并发场景,可引入数据库乐观锁 (添加version字段)或分布式锁,避免并发修改导致的数据不一致。 -
问 :为什么选择MyBatis-Plus?相比原生MyBatis有什么优势?
答 :MyBatis-Plus是MyBatis的增强工具,核心优势有三点:一是简化CRUD操作 ,内置大量通用方法,无需手动编写XML映射文件;二是支持条件构造器 ,通过QueryWrapper实现复杂条件查询,代码可读性更高;三是内置分页插件,一行代码即可实现分页功能,开发效率大幅提升。 -
问 :前后端对接时遇到的最大问题是什么?如何解决?
答 :最大问题是参数传递和响应判断的一致性问题 ,比如400参数解析失败、提示文案矛盾。解决思路是:首先通过浏览器Network面板和后端日志定位问题(前端参数格式/后端注解配置);其次制定统一的接口规范(请求头、响应体格式);最后在前端封装统一的请求方法,后端封装统一的响应工具类Result,从根本上避免类似问题。
六、总结
本项目通过SpringBoot+Vue+MySQL技术栈,实现了二手交易系统的核心功能闭环。开发过程中遇到的字段映射、关键字冲突、前后端交互等问题,均是新手入门前后端分离开发的典型痛点。解决这些问题的过程,不仅是代码能力的提升,更是问题分析和排查思路的积累。
该项目虽小,但覆盖了后端接口开发、数据库设计、前端页面交互、前后端联调等核心环节,是夯实基础、备战面试的优质实战案例。
github地址:点击此处进入项目