☰
校园旧物交易平台全栈实战:Spring Boot+Vue从设计到部署
2026/10/11 8:31:53 网站建设 项目流程

最近帮一位学弟看一个课程设计项目,项目编号是11821,名字叫“多鱼”旧物交易平台。这名字起得挺有意思,多鱼谐音“多余”——二手闲置品嘛,对卖家来说确实有点多余,但放到另一个买家手里可能就是宝贝。一句话总结这个项目:一个面向校园或社区场景的闲置物品交易网站,具备用户注册登录、商品发布浏览、下单购买、收藏留言、后台管理等完整功能。

这类题目在高校课设和毕设里出现频率极高,原因很简单:旧物交易平台既能体现完整的业务闭环,又不会像电商系统那样庞大复杂,是一个性价比非常高的练手题目。我帮学弟把项目完整梳理了一遍,从需求拆分、数据库设计、后端接口到前端页面,踩了不少坑,也总结了一些非常实用的经验。这篇博文就把整个思路和实操过程写清楚,无论你是正准备选这个题目的在校生,还是想拿一个完整项目练手的初学者,都可以直接参考复现。

1. 项目整体设计与思路拆解

1.1 业务需求与用户画像

拿到“多鱼”这个题目,第一步不是急着写代码,而是先理清楚这个平台到底服务谁、解决什么问题。旧物交易不同于全新商品电商,它的核心痛点有三个:信息不对称、信任成本高、交易流程琐碎。卖家希望自己的闲置尽快出手,买家希望淘到性价比高的东西且不被坑,平台要做的就是把两边撮合起来,同时用规则降低信任门槛。

我把目标场景定位在校园或小型社区,这个定位很关键。校园场景用户集中、物品流通频繁,而且用户群体的诚信度相对容易维护,非常适合作为课题项目的假设背景。基于这个场景,系统用户分为三类:普通买家、普通卖家、平台管理员。注意在实际实现中,注册用户既可以买东西也可以卖东西,不需要像大型电商那样强制区分角色,只需要在权限层面区分“管理员”和“普通用户”即可。

核心需求梳理出来就很清晰了:

  • 用户侧:注册登录、浏览商品、搜索分类、查看详情、收藏、下单、管理自己的发布和订单
  • 卖家侧:发布商品、编辑上下架、查看自己商品的成交情况
  • 管理侧:用户管理、商品审核或下架、分类维护、基础数据统计

1.2 功能模块划分

功能模块划分是课的骨架,我习惯用“可用性优先”原则来做:先保证核心交易链路能跑通,再添加辅助功能。多鱼平台的核心链路是:用户登录 → 浏览/搜索商品 → 查看详情 → 下单 → 卖家处理 → 交易完成。围绕这条链路,我把系统拆成五个模块:

用户模块:注册、登录、个人信息维护、密码修改、头像上传。这个模块是所有功能的基础,直接决定后续所有操作的归属。

商品模块:商品发布、编辑、上下架、分类展示、关键字搜索、商品详情、图片上传。这是整个平台信息流的核心,做得好不好直接影响用户体验。

交易模块:创建订单、确认订单状态、取消订单、完成交易。这里要注意旧物交易的特殊性——大部分校园旧物交易是线下见面交付的,所以订单流程不需要设计复杂的物流体系,但买家下单后商品的在售状态要同步变更。

互动模块:收藏商品、商品留言、站内消息通知。这些功能是提升用户粘性的关键,尤其收藏功能几乎是必需品。

管理模块:后台登录、用户列表管理、商品审核与下架、分类管理、统计看板。管理员权限要有独立入口,不能和普通用户混在一起。

这样划分之后,每个模块的边界非常清楚,开发和答辩时都容易讲明白。

1.3 技术选型分析

技术选型是这类项目里最纠结的部分,尤其是第一次做完整项目的人。我推荐的组合是:Spring Boot + MyBatis-Plus + MySQL + Vue 3 + Element Plus,前后端分离架构。下面这个表是我给学弟的选型建议,也附上了理由:

层次推荐方案备选方案选型理由
后端框架Spring Boot 3Python Flask / Django生态成熟、社区资料多、求职认可度高
持久层MyBatis-PlusSpring Data JPA单表CRUD不用写SQL,复杂查询再手写,效率高
数据库MySQL 8PostgreSQL / SQLite支持事务、并发好、课设和线上环境通用
前端框架Vue 3 + Element PlusReact + Ant Design组件丰富、上手快、中文文档友好
鉴权方案JWTSession + Cookie前后端分离标准做法,移动端后续也好扩展
构建工具Maven + ViteGradle + Webpack默认方案,社区支持最好

选这套组合的另一个理由是:整个项目所有环节踩坑都有现成解决方案,不会卡在环境问题上浪费太多时间。如果你对Java不熟,用Python Django做后端也行,核心思路完全一致,只是换了一套表达方式。但对于“设计与实现”这类课设题目,Spring Boot + Vue这套组合在答辩时说服力更强,因为完整的项目结构更接近真实企业开发。

2. 核心细节解析与数据库设计

2.1 数据库表设计详解

数据库设计是这类项目最见功底的部分。第一次做的人最容易犯的错误是“想到哪儿建到哪儿”,导致表之间关系混乱、字段冗余。我的建议是,先在纸上把实体关系画出来再动手。多鱼平台的核心实体有:用户、商品、分类、订单、收藏、留言/评价。表结构如下:

表名说明关键字段
user用户表id、username、password、nickname、avatar、phone、role、status
category商品分类表id、name、parent_id、sort
goods商品表id、title、description、price、original_price、degree、category_id、seller_id、images、status、view_count
trade_order订单表id、order_no、goods_id、seller_id、buyer_id、price、status
favorite收藏表id、user_id、goods_id、create_time
goods_message商品留言表id、goods_id、user_id、content、reply_to
notice站内通知表id、from_id、to_id、content、is_read

每个表都建议带上create_time、update_time两个时间字段,这是通用做法。注意我特意把订单表起名trade_order而不是order,因为order在 MySQL 里是排序关键字,直接用作表名容易踩坑,别到时候再改名字。

字段类型上,价格字段务必用DECIMAL(10,2)而不是FLOAT或DOUBLE,浮点数计算精度会有问题,这是金融行业留下的教训,放到交易场景也一样适用。description字段用TEXT,商品描述通常比较长,VARCHAR(255)不够用。

2.2 商品状态与订单状态机

这张表的状态字段设计,是整个系统业务逻辑的灵魂。我在学弟的项目里刻意把状态做成了显式枚举,而不是散落在代码里的魔法数字。商品表goods.status定义如下:

  • 0 - 审核中:管理员审核通过才上架,适合体现管理员的监管职能
  • 1 - 在售中:正常展示,可被搜索和下单
  • 2 - 已下架:卖家手动下架或管理员强制下架
  • 3 - 已售出:订单生成后同步变更,防止重复购买

订单状态trade_order.status同样用状态机管理:

  • 0 - 待买家确认/待卖家处理:买家下单后生成订单,此时商品状态变为已售出
  • 1 - 交易完成:双方线下碰面交易完成后,买卖双方确认
  • 2 - 已取消:买家取消或卖家24小时内未处理超时取消

这个设计最核心的点在于:下单动作要让“商品已售出”和“创建订单”同时发生,并且要放在同一个事务里。否则就会出现两个买家同时下单同一个商品的并发问题,后面我会专门讲到这个坑。

2.3 索引设计与初始化数据

数据库建成之后别急着写代码,先把索引和初始化数据准备好。索引我给出的建议是:

  • goods表的category_id、seller_id、status分别建普通索引,因为列表页和后台管理页面都会按这些字段筛选
  • trade_order表的buyer_id、seller_id建普通索引,个人中心查订单列表会用到
  • trade_order表的order_no建立唯一索引,保证订单号不重复

初始化数据至少需要准备:一个管理员账号(角色为admin)、一个测试普通用户、5到8个商品分类(数码、图书、生活用品、衣物、体育器材、美妆、其他)、每个分类下几条测试商品数据。有了这些基础数据,系统一跑起来就能看到效果,不用边开发边造数据。

这里要特别注意一个细节:数据库的表结构、初始数据要写成SQL脚本放在项目根目录的 sql 文件夹里,方便别人复现你的项目。评审老师或面试官如果看到你有规范的SQL脚本,第一印象就会好很多。

3. 实操过程与核心环节实现

3.1 项目初始化与环境搭建

拿到的电脑往往是全新的环境,所以我把初始化步骤写成了固定流程,照着做基本不会出问题。先装后端依赖:JDK 17、Maven、MySQL 8。然后创建两个项目目录,前端叫duoyu-ui,后端叫duoyu-server,互相独立,通过HTTP接口通信。

后端的创建直接用 Spring Initializr,勾选以下依赖:Spring Web、MyBatis Framework(如果是Spring Boot 3,配合MyBatis-Plus单独引入)、MySQL Driver、Lombok、Validation。这里要提醒一下:Spring Boot 3 对应的是 MyBatis-Plus 3.5.3以上版本,旧版的MyBatis-Plus在Spring Boot 3下会启动报错。学弟当时就卡在这个版本兼容问题上,我让他换掉版本号后秒解决。

前端的创建比较简单:Node.js 18以上,执行npm create vue@latest创建Vue 3项目,然后引入 Element Plus 和 Axios。开发模式下关键的配置是跨域代理:在vite.config.js里配置server.proxy,把/api开头的请求代理到http://localhost:8080。这样前后端联调时不需要后端配置跨域,也能正常收发请求和数据。

3.2 用户登录与JWT鉴权实现

登录鉴权是全栈项目里最容易讲、也最容易出错的点。多鱼平台采用JWT令牌方案。用户登录成功后,后端生成了一个包含用户ID和用户名的令牌,前端拿到令牌存进localStorage,每次请求在请求头里携带。后端的拦截器统一校验令牌,校验通过才放行。

核心代码逻辑我用伪代码描述,大家可以直接套用:

// 登录接口逻辑 User user = userMapper.selectByUsername(username); if (user != null && passwordEncoder.matches(password, user.getPassword())) { String token = jwtUtil.generateToken(user.getId(), user.getUsername(), user.getRole()); return Result.success(new LoginVO(token, user)); } else { throw new BusinessException("用户名或密码错误"); }

这里必须强调一个安全问题:数据库里的密码绝不能明文存储。用BCrypt加密,也就是Spring Security里提供的BCryptPasswordEncoder,加密后的密码即使泄露也无法直接还原。很多课设项目都把密码明文存在数据库里,答辩时这是硬伤,一定要避免。

登录之后的拦截器写法也不复杂,关键在白名单配置——登录注册接口、商品列表接口、商品详情接口可以匿名访问,其他所有需要用户态的操作都要校验。白名单可以配在配置项里,方便统一维护。

3.3 商品发布与文件上传

商品发布是商品模块的核心,涉及的信息比较多,表单字段包括:标题、描述、分类、成色、原价、售价、图片。前端的表单校验要做好,价格必须大于0,成色用下拉选择(全新、几乎全新、轻微使用痕迹、明显使用痕迹),描述不能超过500字。

图片上传是我帮学弟排查最多问题的地方。我采用的是本地存储方案,这是最简单的方案,但需要注意几点:

  • 后端需要配置上传目录和一个虚拟路径映射,把磁盘路径映射到/upload/**这样的URL上面,否则图片无法通过浏览器访问
  • 生产环境大小限制放宽到5MB,课设阶段可以限制2MB。要注意在application.yml里配置spring.servlet.multipart.max-file-size和max-request-size,否则默认1MB,传一张手机照片就报错了
  • 文件名不要用用户原始文件名,用UUID.randomUUID().toString()重新生成,避免文件名带有奇怪字符导致URL问题
  • 图片存储路径要相对路径,存/upload/xxx.jpg,不要带上盘符的绝对路径,不然换一台机器项目就废了

商品列表接口要支持分类筛选、关键字搜索、分页。多鱼的商品卡片页用Element Plus 的 el-card + el-pagination组合,后端用MyBatis-Plus的分页插件,一行代码就能完成分页。多表联查的地方,比如要带出卖家昵称和头像,需要手写SQL,用@Select注解配合resultType处理,这个不复杂但要提前规划好。

3.4 订单生成的并发控制

下单逻辑是整个项目技术含量最高的部分。我先说一下最容易翻车的写法:前端点击“立即购买”,后端直接执行insert into trade_order,然后再update goods set status='3'。这个写法在并发情况下会出问题——两个买家同时点击购买同一个商品,两个订单都插入成功了,商品状态也被更新两次,但本质上只有一个人应该买到。

正确的做法是把“创建订单”和“更新商品状态”放在同一个数据库事务里,并且在更新商品状态时加上条件判断。看看下面这个示例:

@Transactional public Long createOrder(Long goodsId, Long buyerId) { // 查询商品 Goods goods = goodsMapper.selectById(goodsId); if (goods == null || goods.getStatus() != 1) { throw new BusinessException("商品不存在或已下架"); } // 生成订单号,格式如:20260321+用户ID+随机数 String orderNo = generateOrderNo(buyerId); // 插入订单 TradeOrder order = new TradeOrder(); order.setOrderNo(orderNo); order.setGoodsId(goodsId); order.setSellerId(goods.getSellerId()); order.setBuyerId(buyerId); order.setPrice(goods.getPrice()); order.setStatus(0); orderMapper.insert(order); // 带条件更新商品状态,这是关键 int updated = goodsMapper.updateStatus(goodsId, 1, 3); if (updated == 0) { throw new BusinessException("商品已被抢先下单,请选择其他商品"); } return order.getId(); }

压轴的是那条updateStatus(goodsId, 1, 3),它的SQL长这样:

UPDATE goods SET status = 3 WHERE id = #{goodsId} AND status = 1

意思是:只有当前状态是在售中(1)的商品,才允许更新为已售出(3)。如果有两个并发请求,数据库行锁保证只有一条更新成功,另一条的updated == 0,直接抛出异常回滚事务,订单也不会插入成功。这种方式叫乐观锁思路,不用额外加锁就能避免超卖问题——这也是答辩时老师最爱问的经典考点。

3.5 前端核心页面与交互

前端页面我按“三页一中心”来组织:首页、商品详情页、搜索结果列表页、个人中心。整体风格走简洁路线,底色白色,主色调用偏橙的暖色,呼应二手物品的温暖置换感。

首页是最重要的门面。顶部是搜索栏和分类入口,中间是商品瀑布流卡片,卡片包含商品主图、标题、价格、成色标签。这里要提到一个交互细节:点击卡片进入详情页时,通过路由传参 goodsId,详情页再调接口获取完整数据,不要把整个商品对象直接传给下一个页面。否则刷新页面后状态就丢了,这是前后端分离项目里典型的坑。

商品详情页包含图片轮播、价格成色信息、卖家信息卡片、收藏按钮、留言区。收藏功能的按钮状态要联合查询——前端打开一个商品时,向后端发一个“是否已收藏”的请求,如果已收藏按钮变成红色实心状态,点击后切换为取消收藏。

个人中心用 Element Plus 的 el-tabs 组织:我的发布、我买到的、我卖出的、我的收藏。我买到的和我卖出的数据来源于订单表,通过买家ID或卖家ID查询订单列表再关联商品信息。这里会有N+1查询的问题,建议直接在SQL里把订单和商品信息联查出来,避免循环查询数据库。

4. 常见问题与排查技巧实录

4.1 项目跑不起来的经典环境问题

我帮学弟排查过的问题里,环境问题占了一半以上。最典型的是这几个:

端口冲突:后端的8080端口被占用,启动报Port already in use。排查方式:Windows执行netstat -ano | findstr 8080,查到PID后在任务管理器结束进程,或者直接改后端端口。

连接数据库失败:Access denied for user 'root'@'localhost'通常是密码不对或没有授权远端连接。本地直接检查application.yml里的账号密码是否和MySQL一致。还有一类特殊的是时区问题,连接串加?serverTimezone=Asia/Shanghai可以解决,否则数据库时间差了8小时。

Node版本不兼容:Vite 5以上要求Node 18+,低版本会报Digital Envelope Routines::unsupported错误。注意要把Node升级到LTS版本,别用太旧的版本。这类问题看起来是代码问题,实际是环境问题。

4.2 图片上传失败的三个细节

图片上传是前端开发者第一次做全栈最容易误解的地方。学弟遇到了三个问题,我一一记录如下:

第一个是413错误:上传的图片太大被拒绝。默认上传大小限制是1MB,在application.yml里配置了 multipart 限制后解决。前端展示和上传预览时,建议先压缩一下再上传,可以用el-upload的before-upload钩子做图片压缩,用Canvas把图片压到1280宽度以内,能显著减小体积。

第二个是404访问不到图片:上传成功但浏览器里访问图片地址打不开。这个问题的根源是后端没有把物理磁盘路径暴露成HTTP可访问的URL。需要配置一个资源映射器:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceHandler("file:" + uploadPath + "/"); } }

第三个是服务器重启后图片全部失效:本地存储的临时目录比如/tmp会被系统定期清理。解决办法是把上传目录改到项目外部,比如D:/duoyu-upload/或部署环境的/data/duoyu-upload/,不要放在/tmp这种临时目录下面。代码里上传目录写死一个绝对路径,并用@Value注解从配置文件中读取。

4.3 逻辑删除与外键约束的取舍

我见过很多人设计数据库时给所有表都加上外键约束,看起来严谨,实际给业务代码添了不少麻烦。比如管理员想删除某个违规用户时,如果这个用户已经发布了商品,直接删除会触发外键约束报错。更优雅的做法是使用逻辑删除:给用户表加一个deleted字段(0正常1删除),删除用户时执行update user set deleted=1 where id=?,数据其实还在,但业务查询里默认过滤deleted=0。

MyBatis-Plus内置了逻辑删除支持,在配置文件中开启:

mybatis-plus.global-config.db-config.logic-delete-field=deleted mybatis-plus.global-config.db-config.logic-delete-value=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0

这样所有的查询都会自动带上WHERE deleted = 0条件,不需要你在每条SQL里手写。但要注意,逻辑删除不能解决唯一索引冲突——比如用户名做了唯一索引,逻辑删除后再次注册同一个用户名,会报“用户名已存在”。如果遇到这种情况,可以把唯一索引改成组合索引(username, deleted),或者做删除时把用户名改成旧用户名_random,这样就不会冲突了。

4.4 答辩时的高频追问准备

课设答辩环节比项目本身更考验对设计的理解。我把和多鱼平台相关的答辩高频问题整理成一个清单,方便准备:

问题参考回答要点
为什么用JWT而不是Session?前后端分离架构下后端无状态更易扩展,移动端也能直接复用
下单时怎么防止超卖?事务 + 条件更新状态,更新影响行数为0则回滚
自己的商品自己能买吗?下单接口做了卖家买家身份校验,当前用户是卖家抛异常
密码加密怎么做?BCrypt加盐哈希,不可逆,登录时matches校验
如果用户不上传图片怎么办?前端要求必传一张图,后端也加校验;没有主图商品不允许上架
分页是怎么实现的?MyBatis-Plus分页插件,查询条件自动拼接LIMIT

这些问题无论被问到哪一道,核心逻辑都在代码里写清楚了,只要自己把项目每一行代码都过一遍,回答起来不会底气不足。

5. 项目部署与后续扩展

5.1 本地运行与线上部署

多鱼平台在本地跑通之后,部署上线还需要几步操作。前后端分离项目的部署分为两部分:前端构建成静态文件,后端打成可执行Jar包。

前端在项目根目录执行npm run build,会在dist目录下生成静态资源。后端执行mvn clean package,生成duoyu-server.jar。部署时用Nginx作为Web服务器,把前端静态文件放在Nginx的html目录,后端Jar包用java -jar方式跑在另一个端口,Nginx配置/api路径反向代理到后端端口即可。下面是一个可供参考的Nginx关键配置:

server { listen 80; server_name duoyu.example.com; # 前端静态资源 location / { root /data/duoyu-ui/dist; try_files $uri $uri/ /index.html; } # 后端API反向代理 location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header X-Real-IP $remote_addr; } # 上传的图片文件 location /upload/ { alias /data/duoyu-upload/; } }

数据库连接串记得改成服务器对应的地址和密码,上传目录也要在服务器上创建好并配置对应权限。这套部署流程在本地用虚拟机也能完整模拟一遍,对简历上写“掌握项目部署上线流程”非常有用。

5.2 功能扩展方向

课程设计做到能跑、能演示、能答辩,只是最低要求。如果想要项目更出彩、简历更好看,可以在现有基础上扩展这些方向:

消息通知系统:下单后给卖家发一条站内通知,系统通知表和商品留言表配合使用。如果接入WebSocket,可以做实时提醒,买卖双方在线时立刻看到新订单卡片弹出来。

推荐的简单实现:基于浏览记录和分类偏好做一个简单“猜你喜欢”模块。不用上复杂的机器学习算法,在用户每次浏览详情页时记录一条浏览日志,推荐接口里按浏览最多的分类、排除已购已看的商品查出来即可。这个功能很容易讲,效果也直观。

小程序端:将现有的Vue前端改造成微信小程序版本,后端接口完全复用。小程序端少了浏览器兼容问题,图片上传体验更好,也让项目覆盖了“多端”能力。不过要注意前端代码几乎不能复用,需要用小程序框架重写页面层,工作量会大一些。

扩展功能不需要全做,选择一个最感兴趣的方向做深做透,项目完整度会提升一个档次。

6. 写在最后的经验体会

多鱼这个项目前前后后我陪学弟调了一周,最大的感受是:这类课设题目表面看是写代码,实际上考验的是“从需求到落地”的完整思维链。技术本身没有多难,难的是把页面、接口、数据库之间的逻辑关系理顺,以及遇到报错时敢不敢静下心来看堆栈信息。

如果你正在做类似的项目,我给你三个最实际的建议:第一,数据库表结构先设计好再动手,后面返工的代价远超你想象。第二,下单逻辑一定要写事务,这不是加分项,而是基础要求。第三,答辩前自己把项目从零跑一遍,删掉数据库重新初始化,确认复制出来的项目也能一次跑通。很多人的项目在自己电脑上没事,换台电脑就废了,就是环境依赖没记录清楚。

这类项目的意义在于,它像一面镜子,能照出一个开发者对业务、数据、代码的综合理解。把“多鱼”啃下来,你的全栈能力会有一个质的变化。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询