文章目录
- 📖 前言
- 🎯 项目介绍
- 核心功能
- 界面预览
- 🛠 技术栈
- 🏗 系统架构
- 项目结构
- 💾 数据库设计
- user 用户表
- product 商品表
- orders 订单表
- 🔧 关键技术点
- 1. 组件化开发
- 2. Vue Router 路由守卫
- 3. Axios 二次封装
- 4. Spring Data JPA 方法名自动生成 SQL
- 5. 统一响应结果
- 6. 全局异常处理
- 🐛 踩坑记录
- 坑 1:SpringBoot 4.x 不支持 MyBatis-Plus
- 坑 2:MySQL 保留字 order
- 坑 3:Git 子模块问题
- 坑 4:Vue Router 部署到 Pages 后刷新 404
- 坑 5:JPA 分页页码从 0 开始
- 🚀 部署方案
- 后端
- 前端
- 📊 项目总结
- 完成度
- 学到的
- 后续计划
- 🔗 项目地址
📖 前言
最近花了一周时间做了一个校园二手交易系统,从数据库设计到前端 UI,从后端接口到部署上线,完整走了一遍前后端分离的开发流程。这篇文章记录一下整个项目的技术选型、架构设计和踩坑经验,希望能给做类似项目的同学一些参考。
项目地址:
- 🌐 GitHub:https://github.com/dxiangwiki/campus-trade
- 🚀 在线演示:https://dxiangwiki.github.io/campus-trade/
🎯 项目介绍
一个面向校园场景的二手物品交易平台,支持用户注册登录、商品发布、在线下单、订单管理等功能。
核心功能
- 👤用户模块:注册、登录、个人中心
- 📦商品模块:发布商品、商品列表、关键词搜索、分页展示
- 🛒订单模块:在线下单、订单列表、状态管理
- 🔒权限控制:Vue Router 路由守卫,未登录自动跳转
界面预览
🛠 技术栈
| 分类 | 技术 |
|---|---|
| 前端框架 | Vue 2.6 + Vue Router 3 |
| UI 组件库 | ElementUI 2.15 |
| HTTP 客户端 | Axios 1.x |
| 后端框架 | SpringBoot 4.1 + Spring Data JPA |
| 数据库 | MySQL 9.4 |
| 工具库 | Lombok |
| 部署 | GitHub Pages + GitHub Actions |
🏗 系统架构
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 浏览器 │ HTTP │ SpringBoot │ JPA │ MySQL │ │ (Vue2 SPA) │ ──────► │ 后端服务 │ ──────► │ 数据库 │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ ElementUI │ Controller │ Vue Router │ Service │ Axios │ Repository项目结构
campus-trade/ ├── campus-trade-backend/ # 后端 │ └── src/main/java/com/campus/trade/ │ ├── common/ # 通用(Result、异常处理) │ ├── config/ # 配置(跨域) │ ├── controller/ # 接口层 │ ├── dto/ # 数据传输对象 │ ├── entity/ # 实体类 │ ├── repository/ # 数据访问层 │ └── service/ # 业务逻辑层 └── campus-trade-frontend/ # 前端 └── src/ ├── api/ # 接口封装 ├── components/ # 复用组件 ├── router/ # 路由配置 ├── utils/ # 工具(Axios 封装) └── views/ # 页面💾 数据库设计
user 用户表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 主键 |
| username | VARCHAR(50) | 用户名(唯一) |
| password | VARCHAR(100) | 密码 |
| nickname | VARCHAR(50) | 昵称 |
| phone | VARCHAR(20) | 手机号 |
| create_time | DATETIME | 注册时间 |
product 商品表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 主键 |
| title | VARCHAR(100) | 商品标题 |
| description | VARCHAR(500) | 描述 |
| price | DECIMAL(10,2) | 价格 |
| category | VARCHAR(50) | 分类 |
| status | TINYINT | 0在售 1已售 |
| seller_id | BIGINT | 卖家 ID |
| create_time | DATETIME | 发布时间 |
orders 订单表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 主键 |
| order_no | VARCHAR(50) | 订单号(唯一) |
| product_id | BIGINT | 商品 ID |
| buyer_id | BIGINT | 买家 ID |
| seller_id | BIGINT | 卖家 ID |
| amount | DECIMAL(10,2) | 金额 |
| status | TINYINT | 0待付款 1已付款 2已完成 |
| create_time | DATETIME | 下单时间 |
💡踩坑:订单表名用
orders而不是order,因为order是 MySQL 保留字,用 JPA 会报错。
🔧 关键技术点
1. 组件化开发
前端拆分为 7 个页面 + 1 个复用组件:
views/ # 页面级组件 ├── Layout.vue # 主布局(顶栏 + 内容区) ├── Login.vue # 登录页 ├── Register.vue # 注册页 ├── ProductList.vue # 商品列表 ├── ProductPublish.vue # 发布商品 ├── OrderManage.vue # 订单管理 └── UserCenter.vue # 个人中心 components/ # 复用组件 └── ProductCard.vue # 商品卡片ProductCard.vue通过props接收商品数据,通过$emit触发购买事件,在多处复用。
2. Vue Router 路由守卫
router.beforeEach((to,from,next)=>{constuser=localStorage.getItem('user')constpublicPaths=['/login','/register']if(!publicPaths.includes(to.path)&&!user){next('/login')// 未登录跳登录}else{next()}})3. Axios 二次封装
统一处理请求拦截、响应错误、Loading 状态:
// 请求拦截:开启 Loading + 带用户 IDservice.interceptors.request.use(config=>{loadingInstance=Loading.service({fullscreen:true})constuser=JSON.parse(localStorage.getItem('user')||'null')if(user)config.headers['X-User-Id']=user.idreturnconfig})// 响应拦截:统一处理错误 + 解包 dataservice.interceptors.response.use(res=>{loadingInstance&&loadingInstance.close()constdata=res.dataif(data.code!==200){Message.error(data.msg)returnPromise.reject(newError(data.msg))}returndata.data})4. Spring Data JPA 方法名自动生成 SQL
Repository 只需声明方法名,JPA 自动生成 SQL:
publicinterfaceProductRepositoryextendsJpaRepository<Product,Long>{// 自动生成:SELECT * FROM product WHERE status=? ORDER BY create_time DESC LIMIT ...Page<Product>findByStatusOrderByCreateTimeDesc(Integerstatus,Pageablepageable);// 自动生成:SELECT * FROM product WHERE status=? AND title LIKE %?% ORDER BY create_time DESCPage<Product>findByStatusAndTitleContainingOrderByCreateTimeDesc(Integerstatus,Stringtitle,Pageablepageable);}一行 SQL 都不用写,方法名就是 SQL 语义。
5. 统一响应结果
@DatapublicclassResult<T>{privateIntegercode;privateStringmsg;privateTdata;publicstatic<T>Result<T>success(Tdata){Result<T>r=newResult<>();r.code=200;r.msg="success";r.data=data;returnr;}publicstatic<T>Result<T>error(Stringmsg){Result<T>r=newResult<>();r.code=500;r.msg=msg;returnr;}}6. 全局异常处理
@RestControllerAdvicepublicclassGlobalExceptionHandler{@ExceptionHandler(RuntimeException.class)publicResult<?>handleRuntime(RuntimeExceptione){returnResult.error(e.getMessage());}}Service 层直接throw new RuntimeException("商品不存在"),前端收到的就是{code: 500, msg: "商品不存在"}。
🐛 踩坑记录
坑 1:SpringBoot 4.x 不支持 MyBatis-Plus
现象:用 Spring Initializr 生成项目时,最低只能选 SpringBoot 4.x,但 MyBatis-Plus 3.5.x 只支持到 SpringBoot 3.x。
解决:改用Spring Data JPA(SpringBoot 官方亲儿子),比 MyBatis-Plus 更贴合 4.x。
坑 2:MySQL 保留字 order
现象:实体类@Table(name = "order")启动报错。
解决:表名改成orders,实体类用@Table(name = "orders")。
坑 3:Git 子模块问题
现象:在 monorepo 里推送后,GitHub Actions 报No url found for submodule path。
原因:子目录里残留了.git文件夹,被 Git 误判为子模块。
解决:删除子目录的.git,并git rm --cached后重新git add。
坑 4:Vue Router 部署到 Pages 后刷新 404
解决:路由模式改成hash(URL 带#),Pages 静态托管就能正确处理。
坑 5:JPA 分页页码从 0 开始
现象:前端传pageNo=1,JPA 显示的还是第一页(其实是第 0 页)。
解决:Service 层做pageNo - 1,前端始终从 1 开始,用户无感。
🚀 部署方案
后端
SpringBoot 打包成 jar,java -jar xxx.jar运行,或部署到云服务器。
前端
GitHub Pages + GitHub Actions 自动化部署:
每次git push到 main 分支,GitHub Actions 自动:
- 检出代码
- 安装依赖
npm run build- 上传 dist 到 Pages
工作流配置(.github/workflows/deploy-pages.yml):
name:Deploy to GitHub Pageson:push:branches:[main]jobs:build:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v4-uses:actions/setup-node@v4with:{node-version:'18'}-run:npm installworking-directory:./campus-trade-frontend-run:npm run buildworking-directory:./campus-trade-frontend-uses:actions/upload-pages-artifact@v3with:path:./campus-trade-frontend/distdeploy:needs:buildruns-on:ubuntu-latestenvironment:name:github-pagessteps:-uses:actions/deploy-pages@v4📊 项目总结
完成度
- ✅ 用户注册 / 登录
- ✅ 商品发布 / 列表 / 搜索 / 分页
- ✅ 在线下单 / 订单管理
- ✅ 统一异常处理
- ✅ 跨域配置
- ✅ GitHub Pages 自动化部署
学到的
- 前后端分离:RESTful API 设计,Axios 统一封装
- Vue 组件化:props 传递 + $emit 事件
- JPA 方法命名:优雅地避免写 SQL
- GitHub Actions:CI/CD 自动化部署
- Monorepo:前后端一个仓库,管理方便
后续计划
- JWT 登录态(替代 localStorage 明文)
- 图片上传(本地磁盘 / 阿里云 OSS)
- 订单状态流转(付款 / 收货 / 完成)
- WebSocket 实时消息通知
- 卖家视角(我的发布 / 卖出订单)
- ECharts 数据看板
🔗 项目地址
- GitHub:https://github.com/dxiangwiki/campus-trade
- 在线演示:https://dxiangwiki.github.io/campus-trade/
- 技术栈:Vue2 + ElementUI + Axios + SpringBoot 4.x + JPA + MySQL
如果这个项目对你有帮助,欢迎给个 ⭐ Star!也欢迎在评论区交流。
作者:dxiangwiki
日期:2026-10