基于Vue+SpringBoot+MySQL的校园二手交易系统全栈开发实战

发布时间:2026/10/18 17:06:06
基于Vue+SpringBoot+MySQL的校园二手交易系统全栈开发实战 1. 项目是怎么从“想做一个”变成“真的做出来了”的1.1 为什么选这个题目以及它的真实价值先说说我做这个项目的背景。在学校里待过的人应该都有这种体会大四毕业季一堆考研资料、专业书、小台灯、收纳架闲置下来扔了可惜搬走又太重大一新生入学的时候又得花高价买一堆全新的生活用品。这种需求是真实存在的而且每年都在循环。当时我就想着能不能做一个校内的小平台让这些东西在同学之间流转起来。这个念头本身不稀奇但真正让我决定动手的原因是我想要一个能同时练到前端、后端、数据库设计三个方向的完整项目而不是像平时作业那样只写个页面或者只写个接口。技术栈我选的是 Vue SpringBoot MySQL。这个组合不是拍脑袋定的三个角色各有各的分工Vue负责页面渲染和用户交互SpringBoot负责把业务逻辑封装成接口MySQL负责把数据落盘。换句话说前端是脸面后端是大脑数据库是记忆。对一个想在校招简历上写“完整全栈项目”的人来说这三样东西缺一不可。这个项目的目标用户很明确就是在校学生。所以整个系统的设计要围绕“校内”“二手”“交易”这三个关键词转。所谓校内就是用户要能验证学生身份不然就失去“校内”的意义所谓二手是商品本身要有时效性和状态描述比如几成新、有没有瑕疵所谓交易是得有发布、浏览、下单、确认收货这个闭环不能只是一个商品展示墙。说到这里你可以把这个项目拆成两个视角来看管理员视角和学生视角。学生能注册、登录、发布闲置、浏览商品、收藏、下单、确认收货、管理个人发布管理员能审核商品、管理用户、处理举报。这个权限划分一开始就要想清楚不然后面改起来非常痛苦尤其是数据库字段和接口权限那一层前期设计偷懒后期就是给自己挖坑。1.2 这套技术选型背后我踩过的选择和权衡先把话放这儿Vue SpringBoot MySQL 不是唯一解但是对校园二手交易这个场景来说性价比极高的一组搭配。Vue这边我选的是Vue 2 Vuex Vue Router Axios这套经典组合。为什么不用Vue 3不是Vue 3不好而是Vue 2的生态资料更全面遇到问题搜索解决方案的时候命中率更高。对一个第一次完整做全栈项目的人来说能快速定位问题比用上最新特性更重要。页面交互上我用饿了么的组件库表格、表单、分页这些现成的组件直接拿来用能省掉大量样式上的重复劳动把精力放在业务逻辑上。后端SpringBoot我用的是2.x版本配套MyBatis Plus做持久层。MyBatis Plus对新手特别友好它把单表的增删改查封装好了写代码的时候不用每个方法都写一遍SQL尤其是分页查询一个Page对象传进去就行。对于那些需要多表联查的复杂场景它也能支持手写SQL不会封死你的路。很多人一开始会纠结用JPA还是MyBatis我的观点是如果你更看重SQL的可控性和团队的熟悉度选MyBatis Plus基本不会后悔。MySQL这边主要考虑的是事务和索引。交易系统涉及钱和物订单状态不能乱所以订单表的下单操作必须放在事务里这个在后端代码里用Transactional注解解决。索引这块商品表的分类、发布状态、创建时间三个字段后面一定会频繁用于查询务必建上索引不然数据量一上来首页接口就会肉眼可见地变慢。另一个关键点是文件存储。二手商品一定要有实拍图片不然买家没法判断成色。图片不能直接塞进MySQL的BLOB字段里正确做法是把图片存到服务器的某个目录数据库只存访问路径。这个在小项目里最省事也不用额外引入对象存储服务唯一要注意的就是路径别写死成绝对路径不然后面换个环境部署就全裂了。到这里整个项目的骨架已经在我脑子里立起来了。前端单页应用后端REST接口数据库六张核心表图片走本地存储用户权限用JWT做无状态登录。下面我按实际开发顺序从数据库设计开始一步一步讲清楚。2. 数据库设计六张表怎么撑起整个交易闭环2.1 用户表、商品表、分类表的设计与字段取舍数据库是后端开发的地基。前面的接口写得再花哨表设计不合理后面联查的时候必然后悔。我的做法是先画一张简单的ER图把实体和实体之间的关系捋清楚再动手建表。第一张是用户表(user)。字段我设计为id(主键自增)、username(登录账号)、password(加密存储)、nickname(昵称)、avatar(头像路径)、phone(联系电话)、college(学院或校区)、role(角色字段1表示学生2表示管理员)、status(账号状态0正常1封禁)、create_time(注册时间)。这里有几个细节要说明密码必须用加密方式存储我选的是BCrypt不是明文role字段虽然当前只有学生和管理员两种但设计成整数类型而不是布尔值是为了后续万一要加“超级管理员”或者“商家”角色时不需要改动表结构。第二张是商品表(goods)。核心字段有id、user_id(发布者ID)、category_id(分类ID)、title(标题)、description(描述)、price(价格)、original_price(原价)、quality(成色描述)、images(图片路径多图用逗号分隔)、status(状态字段0在售1已售出2下架3待审核)、view_count(浏览量)、create_time、update_time。价格字段要注意不要用float或double钱这种东西用decimal(10,2)最稳妥否则会出现0.1加0.2不等于0.3的尴尬情况。status字段是这个表的灵魂每一次状态变化都要在代码里明确约束比如只有status为0的在售商品才能被别人下单。第三张是分类表(category)。这个表字段很简单id、name、sort_order。内容可以是预设的书籍教材、数码电器、生活用品、运动器材、美妆服饰、其他。分类做成表而不是直接在前端写死是为了方便以后扩展。有人觉得多一张表多一份麻烦但实际经验是分类数据放在表里你后面调整分类名字、排序改数据库就行不用动前端代码重新发布。第四张是订单表(orders)。这可能是整个系统里最容易出错的一张表。字段有id、order_no(订单编号)、goods_id(商品ID)、seller_id(卖家ID)、buyer_id(买家ID)、price(成交价格)、status(状态0待付款1待发货2待收货3已完成4已取消)、create_time、pay_time、confirm_time。之所以要把goods_id、seller_id、buyer_id都单独拎出来而不是只存一个goods_id是因为订单详情页面需要同时展示商品信息、卖家昵称、买家昵称存冗余字段可以减少联查次数这也是典型的空间换时间思路。第五张是收藏表(favorite)。因为是二手交易平台收藏是个高频操作。字段为id、user_id、goods_id、create_time。查询时直接根据user_id去查关联的goods_id列表然后返回到商品详情接口里。这张表比较轻量但别忘了对user_id和goods_id建联合唯一索引防止同一用户重复收藏同一商品。第六张是评论表(comment)。对于二手交易来说交易评价和留言评论可以合一不用做得太复杂。字段为id、goods_id、user_id、content、reply_to(回复某条评论的ID可空)、create_time。为什么需要一个reply_to字段而不是直接做两级回复表因为实际场景里“评论回复”的需求最多到两层一张表的reply_to就能搞定。你要真的做成无限楼层那得用邻接表模型甚至闭包表对一个小项目来说完全没有必要。2.2 外键到底建不建我的选择和理由建设表的时候有一个争议点要不要在表之间建立外键约束。教科书上通常建议建外键以保证完整性但实际开发中尤其是互联网项目里外键带来的问题比解决的问题多。我最终的选择是不建物理外键只在逻辑层面通过代码控制关联。举个例子删除一个用户之前先查询这个用户有没有未完成的订单有就禁止删除或者改为封禁账号而不是让数据库去级联删除。理由有三点。第一外键约束会让插入和更新操作的性能打折扣因为每次操作都要检查关联表的状态。第二级联删除很危险万一误删一个用户他的所有发布记录全部被连带删掉这种事故没法恢复。第三项目后期如果需要分表或者迁移数据外键会成为巨大的阻碍。当然不建外键的前提是代码逻辑必须自己保证一致性比如下单时要校验订单里关联的商品确实存在并且status是0。通过事务和业务校验来控制效果并不比外键差。2.3 事务的必要性下单流程为什么要放在同一个事务里二手交易系统的核心动作是下单它涉及的操作不止一步先要校验商品状态是在售然后创建订单记录再把商品状态改成已售出最后扣减卖家的库存虽然这里没有库存概念但状态变迁是必须的。这几步操作必须捆绑成一个原子操作要么全部成功要么全部失败。SpringBoot里做这件事非常简单在Service方法上加一个Transactional注解就行。但这里我要提醒你一个很隐蔽的坑Transactional注解默认只在抛RuntimeException时才回滚受检异常不会触发回滚。所以如果你的校验逻辑里用了throws Exception这种方式事务不会生效。正确做法是统一抛自定义的业务异常或者明确指定rollbackFor Exception.class。另一个安全细节下单时要开启乐观锁或者状态校验防止两个人同时看到同一件商品然后一起下单。我的做法是在更新商品状态时用一条带条件的UPDATE语句UPDATE goods SET status 1 WHERE id ? AND status 0。如果这条语句影响的行数为0说明商品已经被别人买走了此时直接抛出“商品已售出”异常。这个方式简单可靠比纯粹在Java代码里加锁要来得实在。3. 后端核心接口设计与业务实现3.1 登录鉴权JWT无状态登录是怎么落地的校园二手交易系统的用户身份认证我用的是JWT方案。为什么不用Session因为前后端分离的项目里Session天然不占优势。前端跑在一个端口后端跑在另一个端口跨域请求带上Cookie本来就麻烦而且Session是存储在服务器内存里的服务重启之后全部失效用户就掉线了。JWT的思路是把用户ID、角色、过期时间这些信息加密成一串Token后端签发的Token由前端保存通常是localStorage前端每次请求时把它放在请求头Authorization字段里传回来。后端通过拦截器统一解析Token要么放行要么返回401。具体实现上我引入的是jjwt这个Java库。生成Token的代码大同小异核心是配置一个签名密钥和过期时间。密钥我建议放在配置文件里不要写死在代码中。过期时间我设置的是7天对于校园内部使用来说是够用的。拦截器要拦住除了登录、注册、商品列表、商品详情、图片访问以外的所有接口因为未登录用户也应该能浏览商品这一点对二手的“逛”场景很重要。还需要处理一个细节Token过期了怎么办我选择了最简单的方案前端在收到401响应时统一跳转到登录页同时清空本地存储的Token。复杂一点的方案是续期Token但对这个项目来说属于过度设计。3.2 商品发布与图片上传本地存储方案实操商品发布是个典型的表单提交场景。前端把文字字段和图片文件分开处理先上传图片拿到图片的访问路径后再把携带图片路径的表单数据提交给后端接口。图片上传接口的设计要注意几个点文件格式限制只接收jpg、png、jpeg防止恶意文件上传、文件大小限制单张不超过5M防止服务器磁盘爆掉、文件命名用UUID或时间戳重命名防止文件名重复覆盖。我用的是UUID 原始文件后缀的组合比如680e8400-xxxx-xxxx-xxxx-xxxxxxxx.jpg。存储目录我建议用相对路径加配置文件的方式配置里写file.upload-dir/data/school-market/images然后在代码里用配置类读取这个值。这样以后换服务器改配置文件就行不用重新编译代码。静态资源映射也要配置好让/images/**这个URL前缀能直接映射到本地磁盘目录这样图片路径就可以直接存成/goods/2024/xx.jpg这种格式前端访问时拼上服务器域名就能展示。我自己在开发中踩过的一个坑是本地上传正常部署到服务器发现图片加载不出来。排查半天发现是Nginx没配置静态资源的location规则。如果你用Nginx做反向代理别忘了加一条location /images/ { alias /data/school-market/images/; }这样的配置。3.3 商品列表与搜索分页、筛选与排序的实现思路首页就是商品列表页它要支撑起“浏览”的核心体验。我实现了一个支持分页、分类筛选、按价格和发布时间排序的接口。参数设计为pageNum、pageSize、categoryId、keyword、sortField、sortOrder。后端用MyBatis Plus的分页插件核心代码非常简单传入一个Page对象再传入一个LambdaQueryWrapper插件会自动生成带LIMIT的SQL并把总记录数一并返回。keyword搜索这里有个实用技巧不要直接用LIKE %keyword%去搜所有字段这样效率极低。我是在goods表里增加了title和description的搜索条件用LIKE匹配这两个字段并且使用了MySQL索引优化中的左前缀规则。完全匹配的关键字还有一个隐藏场景——很多学生搜索“四级真题”时会输入“四级”结果里应该能匹配到“大学英语四级真题汇编”所以LIKE模糊匹配在业务上是必须的不用过度纠结性能数据量小时完全够用。商品状态默认只显示status0的在售商品但管理员接口要能看到全部状态所以查询条件要支持动态传入。这个在MyBatis Plus中就是wrapper.eq(status ! null, status, status)这种写法条件构造器很灵活。3.4 订单流程从下单到完成的状态机设计订单状态机是整个系统里最需要细心的地方。状态流转必须严格单向控制待付款(0) → 待发货(1)买家点击支付虽然校园二手通常线下交易但线上流程要有待发货(1) → 待收货(2)卖家点击发货待收货(2) → 已完成(3)买家点击确认收货任意状态 → 已取消(4)买家取消或被管理员关闭状态变更必须由对应角色触发比如点击确认收货的必须是买家点击发货的必须是卖家后端接口要从JWT里解析出当前用户的ID和订单里的seller_id或buyer_id比对不一致直接抛异常。这个校验不能省前端隐藏按钮只是体验优化后端的权限校验才是安全防线。4. 前端页面拆解与交互实现4.1 页面路由与状态管理Vue全家桶的安排前端路由我用Vue Router页面划分大概是这样首页(商品列表)、商品详情、发布商品、个人中心、我的订单(买/卖两个Tab)、登录注册页、后台管理(管理员专属)。路由配置里最需要注意的是懒加载用import函数动态引入组件而不是把所有组件打包进一个JS文件里。这样首屏加载速度会明显更快尤其是图片多的二手平台页面。状态管理用Vuex主要存储三类数据用户信息登录后从后端接口获取并存入、分类列表首次加载后缓存避免每个页面重复请求、当前搜索条件列表页和详情页之间跳转时要保留关键词。这只是为了功能上减少没必要的重复请求不至于在页面切换时丢失状态。页面跳转时还有一个常见问题从详情页返回列表页往往希望停留在上一次滚动的位置。Vue Router的scrollBehavior可以配置成记录路由栈的滚动位置这个小细节很多人忽略但能明显提升使用体验。4.2 商品发布页面图片拖拽上传怎么做发布页的表单字段包括标题、描述、分类下拉选择、价格、原价、成色描述下拉选择全新/几乎全新/轻微使用痕迹/明显使用痕迹、图片上传多图。图片上传组件我基于ElementUI的Upload组件封装了一层核心配置是action指向后端的上传接口、headers带上Authorization Token、limit限制最多6张、on-success回调里把返回的图片路径push进一个数组里最后提交表单时把图片数组用逗号拼接成一个字符串存储。有个体验细节值得分享上传图片后要立即在页面上预览而不是等表单提交后才回显。ElementUI的Upload组件自带list-typepicture-card模式能直接实现预览和删除省了不少事。4.3 商品详情页浏览、收藏、下单的联动逻辑商品详情页的数据来源是/goods/{id}这个接口返回字段包含商品基本信息、卖家信息、当前登录用户是否已收藏该商品。页面布局大致是左侧图片轮播右侧标题、价格、成色、卖家信息、收藏按钮、下单按钮。收藏按钮的交互是重点如果未登录用户点击收藏要引导去登录而不是直接调接口报401。已登录用户点击收藏时前端根据接口返回的状态判断当前是已收藏还是未收藏切换按钮样式并调用对应的收藏/取消收藏接口这里我用了防抖处理避免用户快速连点时发出重复请求。下单按钮的判定逻辑也要处理如果商品是当前登录用户自己发布的按钮要禁用显示“不能购买自己发布的商品”。如果商品已经售出按钮要显示“已售出”并禁用。前端判断只是体验优化后端同样要在下单接口里做对应校验双保险。5. 开发过程中最容易踩的坑逐个呈堂证供5.1 前端跨域问题一次配置终身受益前后端分离开发跨域是逃不掉的第一道坎。前端跑在localhost:8080后端跑在localhost:9090两者端口不同浏览器默认会拦截跨域请求。我在后端写了一个CorsConfig配置类实现WebMvcConfigurer接口重写addCorsMappings方法允许所有来源访问并开放所有方法和请求头。这里有个坑allowedOrigins不要写成“”并同时允许携带凭证allowCredentialstrue这样组合会导致某些浏览器直接拒绝请求。如果你需要携带Token要么使用allowedOriginPatterns要么明确指定允许的来源列表。开发阶段图省事我用的allowedOriginPatterns()配合allowCredentials(true)实测稳定。5.2 时间格式统一避免时区和格式错乱前后端联调时经常发现时间字段显示成一串数字或者差了8个小时。问题在于Jackson默认序列化LocalDateTime的方式以及MySQL驱动连接字符串里的时区参数。我的解决办法是三步齐走第一MySQL连接串里加上serverTimezoneAsia/Shanghai确保数据库连接时的时区一致。第二在SpringBoot里配置Jackson的全局格式将LocalDateTime统一序列化为yyyy-MM-dd HH:mm:ss。第三前端拿到格式化后的字符串后直接用不要再做new Date()处理否则又会产生时区偏移。这三步配合做完时间显示就稳了。5.3 图片显示不出来的排查套路图片问题看起来很简单但真正排查起来链路很长可能是前端路径拼错、可能是后端映射没生效、可能是Nginx挡住、也有可能是权限拦截器把图片请求拦了。我总结了一套排查顺序先用浏览器直接访问图片的完整URL看能不能打开打不开就去看后端日志有没有报错没报错就去看访问的路径是否命中了静态资源映射如果命中了再看请求是否被拦截器拦截。按这个顺序逐层排除比抓瞎要高效得多。6. 部署上线时的几条硬经验6.1 环境配置与打包命令备忘后端打包我用的Maven命令是mvn clean package -DskipTests打出来是一个可执行的Jar包。如果要在服务器上后台运行用nohup命令启动并指定端口和配置文件。前端打包用npm run build产物是dist目录需要配置Nginx把静态文件指到dist目录同时把/api的请求反向代理到后端的Java进程。这里有一个容易出问题的点前端请求后端的URL不能写死成localhost。我在前端代码里把接口基础地址提取成环境变量通过Vue CLI的.env.development和.env.production两个文件分别配置开发环境指向本地后端生产环境指向服务器地址打包时自动替换。6.2 上线前的安全检查清单上线前我按这个清单过了一遍建议你也照着做密码字段确认加密存储登录接口返回的用户信息里不包含password商品发布、下单、管理等接口都做了登录校验管理员接口额外校验了角色普通用户访问直接返回403上传接口限制了文件类型和大小数据库备份策略确认哪怕只是手动mysqldump都要有关键日志有没有打印尤其是下单失败、支付失败这类异常路径服务器端口和防火墙规则是否放行清单虽然基础但每一条背后都有翻车案例支撑。我见过有人因为登录接口返回了密码字段脱库后明文密码全泄露也见过有人上线之后发现管理员接口谁都能调。小项目也是项目该有的安全意识一个都别省。7. 写在最后几个让我少走了很多弯路的体会7.1 先写完核心链路再考虑锦上添花我第一版就用了整整两周做发布商品、浏览、下单、确认收货这四个核心功能评论、收藏、后台管理全部放在后面。好处是我能保证主链路在任何时候都能跑通不会出现做了很多模块但没一个能完整演示的窘境。对新手来说先跑通一条主链路带来的正反馈比在多个模块里反复横跳要强得多。7.2 日志别偷懒调试时救命的概率极高我之前习惯依赖断点调试觉得打日志麻烦。但一旦部署到服务器上断点根本没法用只能靠日志分析问题。后来我学乖了在关键接口上统一使用注解收集日志或者至少在每个Service方法的入口和出口打印参数与结果。特别是在订单状态变更的代码里加上操作者、变更前后状态、操作时间这三个维度的日志遇到纠纷时能说清楚是谁做了什么。7.3 自己多体验几遍“买家”和“卖家”的完整流程测试的时候千万不能只站在管理员或开发者的角度要模拟真实的用户操作。我试过把发布者、买家、管理员三种角色分别注册登录一遍完整走完发布→购买→确认收货→评价这条链路。过程中真的发现了几个问题比如卖家在“我发布的”列表里看不到自己商品的当前状态比如买家购买后再次进入商品详情页能看到已售出但是在订单列表里找不到该笔订单。这些问题如果不亲历用户视角真的很难靠代码review发现。整套系统从数据库设计到前后端实现再到现在稳定跑在服务器上中间踩过不少坑但也正是这些坑让我把Vue生命周期、SpringBoot事务、MySQL索引这些原本停留在书本上的概念真正内化成了肌肉记忆。如果你也在做类似的全栈项目我最想提醒你的一句话是业务逻辑的边界条件一定要在编码之前想清楚后面你会感谢自己当初多想的那五分钟。

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询