资讯动态

校园二手闲置租售系统实战:从需求拆解到Spring Boot+Vue落地

发布时间:2026/10/2 8:57:08 来源:尧图企业网站定制
大三那年宿舍堆满了考研资料、旧教材和小电器拍照发了几条二手群消息三天没人问价到最后全被收废品的大爷十块钱拉走。就是那一刻我突然意识到校园里的闲置流转根本不是一个微信群能解决的。后来我拉上两个同学花了三个月从零做了一套校园二手闲置物品租售系统一边开发一边在校内试运营最后真跑通了早期用户和订单闭环。这篇文章就把这套系统的完整思路、设计取舍、落地实现和踩过的坑原原本本写出来给想在校内做类似项目的团队一个可复用的参考。1. 项目定位与需求拆解1.1 校园场景为什么值得做二手租售校园和普通社区二手市场的最大区别在于人群极其集中、物品共性极强、信任半径天然存在。一个四千人的学院至少上千人拥有同款教材、同款宿舍小风扇、同款考研资料但信息完全散落在不同的年级群、宿舍群和表白墙评论区里供需匹配效率低到可以忽略。与此同时学生的流动性是固定的每年六七月毕业季集中释放大量闲置每年九十月新生入学又有集中购买需求这种周期性爆发是校园场景独有的。单纯的二手出售其实只覆盖了一半需求。实验室的仪器、体测用的羽毛球拍、拍毕业设计用到的一次性单反、只住一个月的短租台灯这些物品的使用时长很短买下来完全不划算。所以我一开始就打算做成“出售 租赁”双模式出售解决所有权转移租赁解决短期使用权需求。实测下来租赁虽然业务逻辑复杂得多但正是这个功能让系统在同类项目里有了差异化价值。1.2 “出售”和“租赁”本质上是两种业务很多人做二手系统想当然地把租赁理解成“加一个日租金字段”这是最大的误区。出售是瞬时交易下单、付款、交付、确认流程成型后基本没有后续。租赁是持续型交易从起租日到归还日之间有完整的时间跨度中间涉及押金冻结、按天计费、逾期判定、损坏定责、押金退还每一环都是信用风险点。系统里商品就分三种类型仅出售、仅出租、可租可售。前两种实现起来相对简单第三种最麻烦。商品详情页要同时展示售价、日租价、周租价、押金和最长租期买家下单前必须选择交易方式。订单表里也要有一个trade_type字段销售订单和租赁订单走完全不同的状态流转逻辑。如果前期设计时没有把这两个领域模型彻底分开后面要么互相污染要么代码里全是 if-else 分叉维护成本极高。产品原型阶段我花了整整一周梳理各种边界情况最后形成一个核心原则出售看库存租赁看时间。两种模式共用商品库但订单、计价、库存扣减和结算逻辑完全分离。2. 技术选型与整体架构设计2.1 为什么选 Spring Boot Vue 前后端分离技术栈选择上我们对比过几条路线SSM JSP、Django 模板渲染、Spring Boot Vue 前后端分离。最终选的是 Spring Boot Vue 3 Element Plus原因很简单团队里三个人对 Java 后端最熟Vue 生态对校园项目的开发效率最高前后端分离之后小程序端或者移动端 H5 后续可以直接复用同一套后端接口。后端版本用的 Spring Boot 2.7搭配 MyBatis-Plus 做数据访问MyBatis-Plus 对单表 CRUD 的封装度极高写商品、订单这类基础接口基本不用手写 SQL。数据库用 MySQL 8.0缓存用 Redis。登录体系没上 Spring Security 全家桶而是用 JWT 拦截器校园项目没必要把框架堆得太重轻量、能维护才是第一位。前端初期走了些弯路一开始想用 Vue 2 Element UI后来发现 Element Plus 对 Vue 3 的兼容性明显更好配套的中后台模板也更成熟项目做到一半整体升级过一次建议后来者直接从 Vue 3 起步不要被旧的教程带偏。2.2 模块划分与核心数据链路系统拆成五个核心模块用户认证、商品中心、订单中心、信用评价、管理后台。用户认证负责注册登录、实名认证和角色权限商品中心处理发布、审核、上下架、分类检索订单中心是最复杂的模块并行处理销售订单、租赁订单、押金冻结、租金计算和自动确认超时信用评价做双方互评、投诉记录和用户信用分管理后台则处理商品审核、用户管理和举报仲裁。数据流向大致是前端页面 → Controller → Service → Mapper → MySQL热点商品详情和类目列表查完落在 Redis下单时先通过 Redis 预扣库存再异步落订单库。这个链路看着简单但每一步都有细节。比如图片上传一开始想直接传 Base64 字符串后来发现一张 2MB 的照片转成 Base64 之后接近 2.7MB请求体太臃肿最后改成 MultipartFile 直传后用 Nginx 做静态映射数据库里只存访问 URL。2.3 模拟支付模块的取舍学生团队接真实微信支付或支付宝需要营业执照和商户资质审核周期长校园演示项目根本等不起。我们最终做了两套方案一套是支付宝沙箱环境供正式演示时走完整支付链路另一套是系统内置的“虚拟余额 二维码线下转账”方案。买家下单后生成一个假订单状态“待付款”页面展示收款码付款后填写汇款单号卖家确认到账后系统再流转到下一步。这套方案绕开了支付牌照问题又保留了订单状态的完整性实际运营中校园用户也更习惯扫码付款、当面自提的信任交易方式。3. 数据库设计与表结构拆解3.1 商品表租赁与出售字段怎么共存商品表是整个系统的地基。我的建议是不要为了省事把所有商品都塞成一张宽表但校园数据结构相对简单用一张表加可空字段的方式反而最直观。核心 SQL 结构如下CREATE TABLE product ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 发布者ID, title varchar(100) NOT NULL COMMENT 商品标题, category varchar(30) DEFAULT NULL COMMENT 类目如教材/数码/生活用品, description text COMMENT 商品描述, type tinyint(1) NOT NULL DEFAULT 1 COMMENT 1仅出售 2仅出租 3可租可售, price decimal(10,2) DEFAULT NULL COMMENT 售价出售类必填, daily_price decimal(10,2) DEFAULT NULL COMMENT 日租价, weekly_price decimal(10,2) DEFAULT NULL COMMENT 周租价可选, deposit decimal(10,2) DEFAULT NULL COMMENT 押金金额, max_rent_days int(11) DEFAULT NULL COMMENT 最长租期单位为天, stock int(11) NOT NULL DEFAULT 1 COMMENT 库存数量, status tinyint(1) NOT NULL DEFAULT 0 COMMENT 0待审核 1在售 2已下架 3交易中 4已售出, view_count int(11) NOT NULL DEFAULT 0 COMMENT 浏览量, favorite_count int(11) NOT NULL DEFAULT 0 COMMENT 收藏数, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_category_status (category, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有几个反直觉的设计要重点说明。第一stock没有设置成默认 1而是允许在二手场景下出现“1”。不要小看这个字段校园里经常有人一次性出二十本相同教材或者代卖整个宿舍的闲置有库存字段就可以支持这种批量出售场景也方便后面做并发扣减。第二status里把“交易中”单独拉出来是因为租赁订单生效期间商品必须被锁定不能被另一个人下第二单这个状态是租赁模式的命脉。3.2 订单表与状态机设计订单表比商品表更考验设计功力。我拆了两张表销售订单表order_sale和租赁订单表order_rental。一开始想过合成一张试了三天之后果断分开理由是租赁订单比销售订单多一堆时间字段和押金字段混在一起查询时加条件判断和空值校验的痛苦远超建表的成本。销售订单状态机待付款 → 待发货 → 待收货 → 已完成中间穿插已取消和退款申请。租赁订单状态机待付款 → 待取货 → 使用中 → 待归还 → 已归还待退押金→ 已完成中间有逾期标记、纠纷中、已取消。order_rental表的关键字段是borrow_start_date、borrow_end_date、actual_return_date类型必须用 DATE 而不是 DATETIME。租赁按天计费用 DATETIME 会带出几号几点起租的歧义。比如一个台灯租三天从 6 月 1 日到 6 月 3 日DATE 类型天然表达“3 号晚上归还即可”用时间戳反而容易在时区和跨天上翻车。3.3 租赁费用的计算规则租赁计费是业务最核心的规则必须要有一套明确的公式否则客服能被打爆电话。我们的规则定得很直接租金 日租金 × 天数天数 归还日期 - 开始日期 1不足一天的按一天算。周租优惠如果天数大于等于 5按“每周价格”折算但需取整到周剩下不足七天的按日租金补足。例如周租 20 元、日租 5 元租 10 天就是 20 5 × 3 35 元。押金在下单时冻结全额退还的前提是商品外观无损坏、功能正常、按时归还。损坏定责需要双方上传照片凭证管理员介入判断。这套规则看似简单但真正运行之后逾期问题才是最头疼的。如果买家到归还日没有归还系统先自动打上“逾期中”标记按日租金的 1.5 倍累计逾期费同时冻结信用分和后续下单权限。逾期超过 7 天卖家可发起“强制结算”从押金里扣除订单总额和逾期费剩余部分退还买家商品标记为“遗失”。这个判定逻辑是运营一个月后根据真实纠纷总结出来的一开始规则定得太松逾期半个月都没有任何系统动作形同虚设。4. 核心功能模块落地实录4.1 发布链路从图片上传到敏感词过滤商品发布是一个表面简单、细节极多的入口。前端的表单有标题、类目、描述、图片、价格、交易方式、押金、租期等字段后端拿到数据以后要做三重校验。第一重是字段必填和格式价格只能保留两位小数押金不能超过售价的 1.5 倍租期只能在 1 到 120 天之间。第二重是敏感词过滤标题和描述要走一遍基于 DFA 算法的敏感词库校园场景尤其要拦截代写论文、发票代开之类的违规信息。第三重是图片规范单张图片不能超过 5MB支持 jpg、png、webp后端用 Thumbnailator 压缩成最大宽度 1200px 的版本再存储避免详情页加载太慢。发布之后商品默认进入“待审核”状态管理员后台上架或者驳回。为什么要有审核环节因为开放注册的 C2C 平台一旦出现违规商品被投诉责任会落到运营方头上审核虽然慢一点但能拦住大多数风险。后来上线之后发现自动审核通过率有 85% 左右真正需要人工处理的也就教材盗印、电器三无产品这些。4.2 下单并发扣减与库存一致性下单逻辑是整个系统并发压力最大的环节。同一个商品可能同时被多个人点击“立即购买”或者“立即租用”如果没有并发控制库存就超卖了。一开始我用的是最朴素的实现先 select stock判断 stock 0再 update stock stock - 1。上线第一天就在一门最热门的考研资料上暴露了问题三个人同时下单三个人都看到库存剩 1结果全都跳转到支付页。后来改成 MyBatis-Plus 的乐观锁UPDATE product SET stock stock - 1, version version 1 WHERE id #{productId} AND stock 0执行 update 返回影响行数为 1 才继续创建订单否则直接提示商品已售罄。这条 SQL 在校园项目的并发量级下足够稳定Redis 预扣库存反而因为增加了一个分布式事务环节导致代码复杂度和故障点成倍上升后来直接去掉了。记住一个原则并发方案不要追求最先进的要追求最符合自身量级的几百人同时在线的系统一条带条件更新的 SQL 就够顶很久。4.3 租赁订单时间轴与自动任务租赁订单从付款成功那一刻起就开始和时间赛跑了。起租当天生成“待取货”状态买家和卖家约定在校内自提点见面双方确认后点击“我已取货”订单进入“使用中”。到期当天早上 8 点系统自动给双方推送提醒内容包括归还截止时间、归还地点、逾期后果。这里用 Spring 的Scheduled定时任务每分钟扫一次数据库里所有使用中且过期未归还的订单打上逾期标记。这块还踩过一个坑定时任务第一次上线凌晨两点扫库把所有今天到期的订单全部标记成了逾期因为比较条件写的是end_date now()而正常逻辑应该是end_date 当天日期日期类型转换时又没取到当天的零点。修正后的逻辑是先查出status 使用中 AND borrow_end_date 当前日期的订单集合逐个判断是否已经是逾期状态再幂等更新。如果没加幂等定时任务每次重启都会把已处理过的订单再处理一遍用户收到的通知会是重复的。4.4 后台审核与控制台管理后台是整个系统能平稳运转的运维底座。管理员能看到商品审核列表、用户举报、租赁纠纷、订单流水和交易统计。审核列表里最有用的是一个“同款检测”按钮基于标题和类目做简单的相似度匹配一键找出疑似批量发布相同商品的账号。用户管理页支持封禁账号和重置信用分纠纷仲裁页则整合了买卖双方的凭证照片和时间线管理员可以直接裁定押金扣除比例裁定结果自动更新订单状态。5. 上线阶段最值得记的五个坑5.1 图片路径问题本地跑得好好的上线全裂了本地开发时上传的图片都存到了项目根目录的upload文件夹访问的时候直接写/upload/xxx.jpg在 IDE 里一切正常。部署到云服务器后我用java -jar启动服务上传路径变成了 jar 包解压的临时目录重启一次图片全没了。这个问题的根因是项目运行时的工作目录和 IDE 里的工作目录不一致。解决方案是定义一个外部配置项file.upload-path上线时指向/data/app/upload并用 Nginx 把/upload/**映射到这个绝对路径。从那以后我连前端静态资源也用 Nginx 托管了后端的 Tomcat 只负责接口请求。5.2 教材超卖一条 SQL 解决的问题前文已经讲了乐观锁处理库存这里补充一个真实场景。我们运营时上架过一套六本的考研英语真题标价 35 元结果一个中午来了四个订单全部支付成功。排查发现当时库存更新走的是 MyBatis-Plus 的updateById它默认不拼接stock 0条件五个人并发进来谁都能减成功。改成上面的条件更新之后那种“订单比库存多”的问题再也没出现过。另外订单表一定要建唯一索引(product_id, user_id, order_type)防止用户手抖连点两次“提交订单”生成两条一模一样的订单。5.3 租赁跨天计费DATE 和 DATETIME 的坑租赁订单刚上线时borrow_end_date用的是 DATETIME有一单租台灯起租 5 月 1 日晚上 9 点应还 5 月 3 日晚上 9 点。计费逻辑算出来居然要收 4 天的钱因为差值算法把起租时刻和归还时刻之间的实际小时数换算成天数后直接向上取整了。后来统一改成 DATE 类型用Period.between(borrowStartDate, actualReturnDate).getDays() 1计算天数才彻底解决。在校园租赁场景里“天数”永远是日期差而不是时间戳差这一点任何接手代码的人都容易看漏。5.4 缓存穿透空数据也能被打爆商品详情页一开始直接查 MySQL后来加了 Redis 缓存key 是product:detail:{id}。结果有段时间后台日志里出现大量慢查询排查发现是有人也可能是自己人压测用不存在的商品 ID 海量请求每次都穿透到数据库。这是典型的缓存穿透问题解决方案很简单对查不到的 ID也在 Redis 里缓存一个空值设置 5 分钟过期再配合布隆过滤器拦截明显不存在的 ID。校园系统不用把布隆过滤器做得很重直接加空值缓存就足够挡住绝大多数恶意遍历。5.5 搜索排名LIKE 查询慢的优化商品搜索用的是title LIKE %keyword%资料少的时候毫秒级响应上架两千条数据之后搜索一次要两秒多。后来用 MySQL 的全文索引解决建了FULLTEXT KEY ft_search (title, description)查询改写为MATCH(title, description) AGAINST(keyword IN NATURAL LANGUAGE MODE)响应时间降到 100ms 以内。再往后量大起来可以再引入 Elasticsearch但校园项目做到索引优化这一步已经完全够用。搜索排序上按“浏览量 × 0.2 收藏数 × 3 成交数 × 10”生成一个热度分再混合上架时间做倒序既能保证基础质量也照顾到新发布的商品有露脸机会。6. 运营推广与冷启动经验6.1 冷启动先铺供给再拉流量校园项目最忌讳一上线就到处发传单结果用户点开一看里面就三件商品转头就走。我当时第一步是“扫楼收闲置”。挨个宿舍问有没有要出的教材、电器、健身器材承诺代拍照片、代写文案、上架后卖出再收 10% 服务费。三天收了 160 多件商品全部按平台规格整理上线。供给量过了 100 这个门槛之后用户进来才有东西可逛后面的推广才不是白做。6.2 毕业季专项活动每年五到七月是校园二手流转的黄金窗口系统专门做了一个“毕业季宿舍集市”版块学生可以一键发布整屋闲置平台自动生成打包清单和摆摊海报。线下配合做了一次“扫码下单、两天内校内到货”的驿站运营教材类订单在毕业季占了总量的 40% 以上。针对大一新生九月开学前上线了“教材预售”功能学长学姐提前上架教材新生按课程号搜索直接下单完美承接了毕业季和开学季两个爆发期。6.3 信用体系与纠纷处理平台没有接第三方的信用分因为学生的数据不在开放的信用体系里所以自建了一套很简单但有效的信用规则。初始信用分 100 分每完成一单正常交易加 1 分逾期未归还一次扣 10 分被管理员仲裁为责任方扣 20 分。信用分低于 70 分就不能发布租赁商品低于 60 分禁止下单。纠纷处理遵循一个原则看凭证而不是听故事。买家取货时必须拍照上传商品现状归还时拍归还照片运营侧只依据双方凭证截图做判断管理员有一个专门的仲裁页面可以查看订单全部时间线这样的裁定结果双方接受度都高。我个人在实际开发中最大的体会是租赁模块的复杂度远超预期任何和时间、押金、信用挂钩的业务都不能想当然地套用普通交易逻辑。系统跑完一整届毕业季后我明显感到相比“把所有功能都做出来”更难的是“在关键节点上做减法”。比如砍掉站内聊天、砍掉复杂推荐算法反而让核心交易链路更顺畅。如果你也想做校园闲置交易系统我建议不要急着堆功能先把出售和租赁两种交易模型吃透再设计表结构再写代码。后续这套系统还可以往社团活动设备共享、考研资料代售、宿舍小卖部入仓这几个方向扩展每一步都不缺真实需求关键是把基础交易闭环做扎实。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑