校园二手交易系统实战开发全记录(vue+SpringBoot+MySQL)

校园二手交易系统实战开发全记录(SpringBoot+Vue+MySQL)

github地址:点击此处进入项目

去年,我做了一个校园二手交易系统,今天给大家,分享一下项目细节

文章目录

一、项目概述与技术栈选型

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请求体)。

  • 解决方案

    1. 前端配置Axios请求头,指定数据格式为JSON;
    2. 后端接口使用@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):

    javascript 复制代码
    import 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表创建SQL

    sql 复制代码
    CREATE 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)

    java 复制代码
    import 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-formel-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)

    javascript 复制代码
    async 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位纯数字,从源头拦截非法数据。

  • 核心代码 :前端手机号校验逻辑

    javascript 复制代码
    async handleRegister() {
        // 手机号格式校验
        if (this.registerForm.phone && !/^1[3-9]\d{9}$/.test(this.registerForm.phone)) {
            this.$message.error("请输入11位有效手机号!");
            return;
        }
        // 后续注册逻辑...
    }

三、全链路功能验证

1. 验证步骤

  1. 用户注册 :前端填写用户名、密码、手机号,点击注册 → 后端加密密码并保存 → 数据库user表新增记录;
  2. 商品发布 :填写商品标题、价格、卖家ID,点击发布 → 数据库goods表新增记录,状态为0(在售)
  3. 订单创建 :填写商品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,实现图片上传、存储、回显功能,完善商品信息;
  • 订单状态管理:新增"付款""取消订单"接口,支持订单状态流转(待付款→已付款→已完成);
  • 个人中心模块:开发"我的发布""我的订单"查询接口,前端实现对应页面,完善用户操作闭环。

五、面试高频问题与解答

  1. :项目中如何保证数据一致性?比如创建订单时商品状态不会被重复修改?
    :当前通过业务逻辑校验 保证------创建订单前先查询商品状态,仅当状态为"在售"时才执行创建逻辑并更新状态;若后续面临高并发场景,可引入数据库乐观锁 (添加version字段)或分布式锁,避免并发修改导致的数据不一致。

  2. :为什么选择MyBatis-Plus?相比原生MyBatis有什么优势?
    :MyBatis-Plus是MyBatis的增强工具,核心优势有三点:一是简化CRUD操作 ,内置大量通用方法,无需手动编写XML映射文件;二是支持条件构造器 ,通过QueryWrapper实现复杂条件查询,代码可读性更高;三是内置分页插件,一行代码即可实现分页功能,开发效率大幅提升。

  3. :前后端对接时遇到的最大问题是什么?如何解决?
    :最大问题是参数传递和响应判断的一致性问题 ,比如400参数解析失败、提示文案矛盾。解决思路是:首先通过浏览器Network面板和后端日志定位问题(前端参数格式/后端注解配置);其次制定统一的接口规范(请求头、响应体格式);最后在前端封装统一的请求方法,后端封装统一的响应工具类Result,从根本上避免类似问题。

六、总结

本项目通过SpringBoot+Vue+MySQL技术栈,实现了二手交易系统的核心功能闭环。开发过程中遇到的字段映射、关键字冲突、前后端交互等问题,均是新手入门前后端分离开发的典型痛点。解决这些问题的过程,不仅是代码能力的提升,更是问题分析和排查思路的积累。

该项目虽小,但覆盖了后端接口开发、数据库设计、前端页面交互、前后端联调等核心环节,是夯实基础、备战面试的优质实战案例。

github地址:点击此处进入项目

相关推荐
ruleslol13 小时前
MySQL的段、区、页、行 详解
数据库·mysql
while(1){yan}13 小时前
MyBatis Generator
数据库·spring boot·java-ee·mybatis
奋进的芋圆13 小时前
DataSyncManager 详解与 Spring Boot 迁移指南
java·spring boot·后端
それども13 小时前
MySQL affectedRows 计算逻辑
数据库·mysql
是小章啊13 小时前
MySQL 之SQL 执行规则及索引详解
数据库·sql·mysql
计算机程序设计小李同学13 小时前
个人数据管理系统
java·vue.js·spring boot·后端·web安全
小刘爱搬砖14 小时前
SpringBoot3 + GraalVM安装和初次打包
spring boot·graalvm
JosieBook14 小时前
【Vue】09 Vue技术——JavaScript 数据代理的实现与应用
前端·javascript·vue.js
Eason_Lou15 小时前
webstorm开发vue项目快捷跳转到vue文件
ide·vue.js·webstorm