拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot+MyBatis公共自行车租赁系统:从数据库设计到部署实战

Spring Boot+MyBatis公共自行车租赁系统:从数据库设计到部署实战

1. 项目概述与核心需求拆解

1.1 这到底是个什么系统

每年到毕设季,我都会收到不少类似的问题:老师给了一个"某某管理系统"的题目,感觉很简单,真做起来又不知道从哪下手。泗洪县公共自行车在线租赁管理系统就是这类题目的典型代表——表面上是个管理系统,实际上里面藏着租赁流程、计费逻辑、车辆状态流转这些真正的业务难点。

这个项目解决的问题很清晰:泗洪县投放了一批公共自行车,分布在县城各个站点,市民取车、骑行、还车都需要一个线上平台来支撑。系统要管住三类东西:车辆在哪、车辆什么状态、谁在用什么车。再往后延伸,就是费用怎么算、记录怎么查、管理员怎么维护数据。把这几个问题想透了,项目骨架就出来了。

这套系统适合谁参考?如果你是计算机相关专业的应届生,拿这个题目做毕设,读这篇能帮你避开大部分坑;如果你是刚入行的后端开发,想找一个练手项目,这套系统的业务复杂度也刚好合适——比纯CRUD有深度,又不像大厂微服务那样一上来就把人劝退。

1.2 核心角色与功能边界

很多同学拿到题目就急着建项目、写代码,结果写到一半发现"咦,这个功能好像没地方放"。我建议先做一件事:把角色和功能边界写在纸上。

这个系统按业务来分,至少有四类核心功能:

  • 用户端(普通市民):注册登录、个人信息维护、余额充值、扫码借车、站点还车、租车记录查询。
  • 管理端(系统管理员):自行车管理(新增、编辑、报废、报修)、站点管理、用户管理(冻结/解封)、租还订单查询与异常处理。
  • 运营端(可选加分项):车辆调度管理,把车辆从满站点调度到空站点,并生成调度记录。
  • 统计报表:租车量趋势、热门站点排行、营收统计、车辆使用率。

我特别建议你在论文里加"运营调度"这个角色。为什么?因为绝大多数同学做的公共自行车系统只有用户+管理员两个角色,你要是把调度员这块做出来,业务闭环就完整了——有人借车造成站点车辆变少,调度员补车,车辆状态恢复正常。这在答辩时是一个很能讲的业务亮点。

功能边界理清楚后你会发现,这项目本质上就四件事:用户管理体系、车辆状态机、租还订单流水、后台数据统计。后面的所有代码,都是围绕这四件事展开的。

2. 技术选型与整体架构

2.1 为什么选 Spring Boot 而不是别的

题目已经限定用 Spring Boot 了,但我想说说为什么这个选择本身是合理的。Spring Boot 最大的价值不是新,而是"省事"。它通过自动装配把 Spring 时代的 XML 配置大量收敛掉,内嵌 Tomcat,打一个 jar 包就能跑。对于毕设这种开发周期短、单人完成的项目来说,这个特性太重要了——你不需要花两周时间去折腾环境,可以把精力全部放在业务代码上。

再说自动装配原理。很多同学在简历上写"熟悉 Spring Boot",一问自动装配就答不上来。其实核心就一句话:@SpringBootApplication里的@EnableAutoConfiguration会去读所有 jar 包里的META-INF/spring.factories(Spring Boot 3.x 是AutoConfiguration.imports),按条件注解@ConditionalOnClass、@ConditionalOnProperty决定哪些配置类生效。你引入spring-boot-starter-web后,DispatcherServlet、Tomcat、Jackson这些配置类发现类路径里有对应的类,就自动加载。理解这一点,答辩的时候被问到"Spring Boot 为什么能自动配置"就不会卡壳。

2.2 配套技术栈和版本选择

这个项目的标准配法是:Spring Boot + MyBatis + MySQL + Vue + Element UI。前端用 Vue 做页面,后端提供 RESTful 接口。

版本选择这里必须多说一句,因为"Spring Boot 版本太高"真的是我见过最多的坑。很多同学图新鲜直接上 Spring Boot 3.x,然后发现:

  • 3.x 强制要求 JDK 17 及以上,如果你的机器还是 JDK 8,直接跑不起来。
  • 3.x 把javax.*换成了jakarta.*,网上大量的老教程代码直接报错。
  • 3.x 里 MyBatis 的官方集成包从mybatis-spring-boot-starter换成了mybatis-spring-boot-starter适配版本,一不小心版本对不上就启动失败。

我的建议是:毕设求稳,直接用 Spring Boot 2.7.x + JDK 8 + MyBatis 1.3.x。别觉得版本旧就是技术落后,把自己的核心业务做扎实,比追新版本有用得多。如果你确实想上 3.x,也行,但一定确认 JDK 版本是三件套里最先检查的。

2.3 项目目录结构与分层设计

我见过太多毕设代码长这样:所有 Controller 塞在一个包,SQL 全部写在 Service 里,实体类一个 Lombok 注解都没有。这种代码答辩时老师翻两页就不想看了。

建议按这样的分层来组织:

com.sihong.bike ├── controller // 接口层,只做参数接收和结果返回 ├── service // 业务层,写核心逻辑 │ └── impl // Service 实现 ├── mapper // MyBatis 数据访问接口 ├── entity // 数据库实体 ├── dto // 传输对象,比如登录请求、租车请求 ├── vo // 视图对象,返回给前端的组装数据 ├── config // 配置类,如 MyBatis、CORS、拦截器 ├── common // 公共类:统一返回结果、异常处理、工具类 └── BikeApplication // 启动类

Controller 里别写业务逻辑,Service 里别直接操作 HttpSession 和前端参数,Mapper 接口和 XML 一一对应。分层清楚之后,你会发现写代码的速度反而更快——因为每段代码该放哪、该干什么,你根本不用想。

3. 数据库设计与核心表结构

3.1 实体关系梳理

数据库设计是这类系统最见功力的一环。我见过很多同学的表设计,最大的问题是"状态全靠感觉"——车辆有没有被借出,居然靠订单表里有没有未还记录来判断,这其实很危险。

核心实体关系其实不复杂:用户表、站点表、车辆表、订单表、充值记录表,再加上一张调度记录表(如果做运营端的话)。关系主要是三条:

  • 一个站点有多辆车,车辆归属于站点。
  • 一个用户有多条租借订单。
  • 一条订单关联一个用户、一辆车、一个取车站点和一个还车站点。

这里有一个关键的建模决策:车辆和站点是不是强归属关系?我的做法是车辆表里存一个current_station_id,表示当前所在站。这样还车时更新这个字段,统计车辆在哪个站点就非常简单。要是不做这个字段,每次查"某站现在有多少车"都得去订单表里算,性能差还容易错。

3.2 核心表设计与关键字段说明

下面这几张表是这个系统的命脉,字段设计值得你多花时间:

用户表t_user

CREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, phone VARCHAR(20), balance DECIMAL(10, 2) DEFAULT 0.00, status TINYINT DEFAULT 1 COMMENT '1正常 0冻结', create_time DATETIME );

注意几点:密码字段长度留到 100,因为 BCrypt 加密后的字符串有 60 位;余额用DECIMAL(10,2),绝对不要用FLOAT,否则金额会出现 0.1+0.2 不等于 0.3 的经典精度问题;状态字段用TINYINT比用字符串好,理由很简单——省空间,而且状态机流转时用数字判断更干净。

车辆表t_bike

CREATE TABLE t_bike ( id BIGINT PRIMARY KEY AUTO_INCREMENT, bike_no VARCHAR(30) NOT NULL UNIQUE, station_id BIGINT, status TINYINT DEFAULT 1 COMMENT '1可租 2已借出 3维修 4报废', create_time DATETIME );

车辆状态只有四个,但这四个状态之间的流转规则是业务核心:可租的车才能被借出,借出后变成已借出;还车时必须把状态改回可租,同时更新station_id;被报修的车不能参与租借;报废车只能由管理员处理。这个状态机一定要在 Service 层写清楚,不能靠前端控制。

订单表t_rental_record

CREATE TABLE t_rental_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, bike_id BIGINT NOT NULL, rent_station_id BIGINT, return_station_id BIGINT, rent_time DATETIME, return_time DATETIME, duration_minutes INT, amount DECIMAL(10, 2), status TINYINT COMMENT '1租赁中 2已还车 3异常', create_time DATETIME );

这张表是计费的核心。免费时长、超时费率这些参数不要写死在 SQL 里,建议在系统配置表里存一份,比如free_minutes=30、hourly_fee=1.00,这样后期调价不用改代码,答辩时也能说"系统支持动态计费配置"。

3.3 索引和事务的设计思路

索引方面别贪多,抓两条主线就行:订单表上user_id加索引,因为用户查自己的租车记录是最频繁的查询;bike_id和status组合索引也可以加一个,但如果你只在查询车辆状态时用,单列索引其实就够。

事务方面重点是租车和还车这两个操作。租车不是简单的 insert 一条订单就能完事,它涉及到:检查车辆状态、检查用户状态、生成订单、更新车辆状态。这四个步骤谁都不能少,其中任何一步失败,前面成功的操作都得回滚。这就是为什么我建议在 Service 层方法上用@Transactional,而且只在真正需要事务的地方用——不是所有查询都加,加了反而拖慢性能。

4. 核心功能模块与实操实现

4.1 注册登录与 JWT 鉴权

用户模块是每个系统都有的,但很多同学做得太"裸"——前端跳个登录页,后端校验下用户名密码就完事了。还是那句话,毕设要体现出工程思维,鉴权这块建议用 JWT。

实现思路不复杂:用户登录成功后,后端用密钥生成一个带过期时间的 Token 返回给前端;前端存在本地,每次请求在请求头里带上Authorization: Bearer xxx;后端写一个拦截器,统一解析 Token,校验通过就放行,并把用户信息塞到请求上下文里。

这里有个容易踩的坑:Token 密钥不要写死在 Controller 里,放配置文件的jwt.secret字段里,后面改起来方便。另外密码一定要加密存储,用 Spring Security 里的BCryptPasswordEncoder就行,不要自己写 MD5。答辩时老师大概率会问"密码是怎么保存的",你答"BCrypt 加盐哈希,每次加密结果不同,即使数据库泄露也无法逆向",这就是标准答案。

4.2 公共自行车的完整租还业务流程

租车业务流程是这套系统里最有说头的地方。前端扫码或者选车后,调用POST /api/rental/rent接口,后端要做的事按顺序是:

  1. 从 Token 里解析出当前用户,校验用户状态是否正常。
  2. 查车辆信息,确认车辆状态是"可租"。
  3. 查用户余额是否为负数,或者是否有未还车辆,防止有人同时借两辆。
  4. 生成一条订单,状态设为"租赁中",记录取车站点。
  5. 更新车辆状态为"已借出",清空当前站点编号。
  6. 返回订单号给前端。

这里有一个细节我建议你注意:第 3 步查"是否有未还车辆",其实更严谨的做法是在订单表上给user_id和status加一个联合约束,或者用一个 Redis 锁来防止并发下用户同时提交两次租车请求。毕设如果不想引 Redis,最简单的办法就是在 Service 方法上加synchronized同步,虽然性能不咋样,但逻辑上是安全的。答辩时能把这个并发问题讲出来,是非常加分的。

还车业务流程比租车还要多几个步骤:

  1. 校验订单存在且状态为"租赁中"。
  2. 校验还车站点存在,且站点当前车辆数小于容量上限。
  3. 更新订单:填还车时间、还车站点,计算骑行时长。
  4. 按计费规则计算金额:免费时长内不收费,超出部分按小时计费,不满一小时按一小时算。
  5. 用户余额扣减费用,订单状态改为"已还车"。
  6. 更新车辆状态为"可租",归入还车站点。

计费这里有个最经典的坑——时长计算。直接用System.currentTimeMillis()相减再除以 60000 看起来没什么问题,但如果你后面要统计"日均租车时长",时区一乱数据就全错了。建议后端统一用LocalDateTime,数据库字段也对应DATETIME,前端只做展示不做计算,所有时间以服务器为准。

4.3 后台管理模块和统计报表

管理端就是标准的管理系统套路:自行车管理、站点管理、用户管理、订单管理,都是Controller + Service + Mapper的增删改查。但这里有两个功能值得做得细一点:

一个是站点容量校验。站点表里要有capacity字段,还车时检查车辆数是否已满,满了就提示用户"该站点车位已满,请前往附近站点还车"。要是没这个功能,车辆越来越多全都堆在一个站,系统就失真了。

另一个是数据统计报表。用 ECharts 做一个柱状图展示近 30 天的租车量趋势,做一个饼图展示各站点租车占比,再做一张表格展示营收排行。后台接口写好之后,前端直接对接。这块做出来的效果很直观,答辩演示的时候一眼就能看出系统的价值。

4.4 定时任务:让系统更"智能"

Spring Boot 做定时任务非常顺手,核心就两个注解:在启动类上写@EnableScheduling,在方法上写@Scheduled(cron = "0 0 2 * * ?")。这个系统里定时任务有两个比较自然的应用场景:

  • 每天凌晨统计前一天的租车数据,生成日报存入统计表。
  • 定期扫描"租赁中"订单,如果超过 24 小时未还车,自动把订单标记为异常,推送提醒管理员去线下核实。

我能理解有的同学觉得定时任务不是必做功能,想砍掉。但我的建议是留着,因为这是你在答辩时说"系统有工程化能力"的凭证,而且实现成本很低,性价比极高。

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

这个部分我汇总一下我做类似项目时踩过的坑,还有平时帮同学看代码时最常见的问题,基本覆盖了从开发到部署的全过程。

5.1 Spring Boot 版本和依赖冲突

前面说了版本问题,这里再补充一种情况:Maven 依赖冲突。典型表现是启动时报NoClassDefFoundError或ClassNotFoundException,但代码本身看起来没问题。排查办法是:mvn dependency:tree看依赖树,找出哪些依赖被重复引入或者版本不一致。

还有一个很隐蔽的坑是 Lombok 和 Java 版本不匹配。你用 JDK 8 但 Maven 里配置的编译插件是 17,或者 Lombok 版本太老,就会出现getter方法找不到的诡异编译错误。我的习惯是 Lombok 直接上 1.18.24 以上版本,省心。

本地开发时另一个高频问题是端口被占用。Spring Boot 默认跑在 8080,但你机器上可能已经有别的服务占着。别去改代码,启动时加参数最省事:java -jar xxx.jar --server.port=8081,或者 IDEA 里在 Edit Configurations 的 Program arguments 里加--server.port=8081。

5.2 MyBatis 的坑:Mapper 扫描和 XML 绑定

MyBatis 最常见的报错是Invalid bound statement (not found)。这个错误 90% 的原因是 Mapper 接口和 XML 文件没有对应上。检查三处:第一,接口的全限定名和 XML 的namespace完全一致;第二,接口方法名和 XML 里的操作 id 完全一致;第三,XML 文件有没有被输出到 target 目录。

第三点特别容易忽略。如果你把 XML 放在src/main/java目录下,Maven 默认不会把它打包到 classes 里。解决办法是在pom.xml的 build 里加资源配置,或者直接把 XML 放在src/main/resources/mapper目录下。我推荐后者,一劳永逸。

还有一个 MyBatis 相关的小细节:参数传递。单个参数可以直接用#{id},多个参数就必须加@Param("xxx"),否则报BindingException。另外 SQL 里if标签里判断参数是否为 null 时,参数名要跟@Param保持一致,别漏了。

5.3 日期格式和数据精度问题

Spring Boot 返回 JSON 时,LocalDateTime默认是一串数字数组,前端拿到根本没法展示。解决方式是在配置文件里加:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

但注意这个配置只对java.util.Date生效,对LocalDateTime不生效。LocalDateTime需要在字段上加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss"),或者全局注册 Jackson 的 JavaTimeModule。这个小问题,能卡住不少人半天。

金额精度那块再强调一遍:数据库用DECIMAL,Java 用BigDecimal,进制转换和计算都用 BigDecimal 的方法,别用double做金额计算。这不是小题大做,是真实的资金安全问题,答辩的时候提一句"为什么不用 double",老师就知道你懂行。

5.4 前后端联调:CORS 和打包集成

本地开发时前端 Vue 在 3000 端口跑,后端在 8080 端口跑,前端请求后端必然遇到跨域问题。后端写一个 CORS 配置类,允许指定来源跨域即可。注意allowedOriginPatterns在 Spring Boot 2.4 之后不能用allowedOrigins("*")带 withCredentials 的组合,这是个老版本迁移到新版本时容易踩的坑。

部署的时候,有一个非常实用的技巧:把 Vue 项目npm run build生成的dist目录里的文件,直接复制到 Spring Boot 的src/main/resources/static下,再重新打包。这样前后端就合并成同一个 jar 包,部署时只需要一个java -jar命令就全跑起来了,不用单独布 Nginx。毕设演示的时候,这一招能让你的部署流程看起来特别干净。

6. 项目部署与答辩准备心得

6.1 打包部署的完整流程

先梳理一遍从代码到可运行的完整流程,这一步建议你在答辩前至少完整走两遍:

  1. 检查application.yml里的数据库配置,确认连接的是演示用的数据库,密码正确。
  2. 如果前端已经合并到 static,直接 Maven 打包:mvn clean package -DskipTests。
  3. 到 target 目录找到生成的 jar 包,用java -jar xxx.jar启动。
  4. 浏览器访问http://localhost:8080,先跑一遍核心流程:注册、登录、租车、还车、看记录。

这里有一个我反复吃亏后总结的经验:演示前一定要用脚本把数据库重置一遍。具体做法是写一个.sql初始化脚本,里面包含建表语句和演示数据,答辩那天早上先执行一遍,保证数据干净、时间戳合理。别用你平时调试留下的脏数据去演示,到时候订单状态乱七八糟,老师一眼就看出来你系统没做数据管理。

6.2 演示脚本的设计

演示不要照着功能清单一个一个点,那太像说明书。我建议你设计一条"业务故事线":

  • 先用一个普通用户账号登录,演示个人信息和余额充值。
  • 选一个站点,查看实时车辆列表,演示租车。
  • 换一个站点演示还车,页面展示费用明细和余额变化。
  • 切到管理员账号,演示车辆管理和订单查询。
  • 最后切到统计报表页,展示租车趋势和站点排行。

这条线走下来,整个系统的业务闭环就在老师面前完整呈现了。期间你要顺手解释关键设计:为什么租车要校验用户和车辆双重状态,为什么还车要检查站点容量,为什么金额用 BigDecimal。

6.3 论文写作的几个加分点

论文部分我不多展开,只点几个容易被忽略的加分点:

  • 需求分析里画好用例图,明确用户、管理员、调度员三种角色。
  • 数据库设计章节放 ER 图和表结构说明,特别是状态字段的注释要写清楚状态含义。
  • 核心功能环节加时序图,比如租车和还车的时序图,这是老师判断你"真做过"的重要依据。
  • 测试章节不要只写"系统功能正常",可以加一段接口并发测试的简单分析,比如 Jmeter 压测 50 个并发租车请求,看系统是否有异常。有数据,论文就有说服力。

7. 最后的几点实在话

这个项目做完一遍,我的体会是:公共自行车租赁系统表面上看是个毕设题目,实际上是一整套业务逻辑的训练场。你处理的不只是增删改查,而是一个车辆状态怎么随时间流转、订单怎么在多个条件约束下安全生成、金额怎么在保证精度的前提下被计算扣减的完整过程。

最后再分享一个小技巧,也是我每次带人做这类系统必说的一句话:先做通一条主链路,再做完整系统。什么意思?就是你第一天不要想着把五张表全建了、十个接口全写了,你先用最简单的方式把"用户注册 → 登录 → 租车 → 还车 → 计费 → 查记录"这六步跑通。哪怕页面丑一点,哪怕接口写得不优雅,先把闭环打通。闭环通了,你的信心就有了,后面加什么功能都是增量工作;闭环不通,就算你写了 20 个接口,系统也是散的。

这套方法不止适用于这个毕设,也适用于你以后做任何新项目。业务永远是第一位的,技术只是实现业务的手段。把这句话想明白,你做的就不只是一个能过答辩的系统,而是一个能让你真正学到东西的项目。

返回列表