简介:这是一套面向计算机专业本科生的2025届毕业设计级全栈项目资源,聚焦租车业务场景,解决车辆租赁服务中用户预订、订单管理、车辆调度与后台监管等核心需求,适用于课程设计、毕设开题与SpringBoot+Vue工程实践学习。资源共6个文件,含3个ZIP(分别封装前后端源码与返修材料)、1个MP4系统操作录屏、1个SQL数据库脚本及1个DOCX需求文档,总大小98.04MB,结构清晰、模块完整,便于快速部署与代码研读。已有108人学习下载,配套B站双视频教程(系统演示+启动部署),覆盖从环境配置、数据库导入到前后端联调的全流程;需求文档详述功能边界与用例流程,SQL脚本适配MySQL8,源码基于SpringBoot3与Vue.js3 Composition API开发,具备现代Web应用典型分层架构与RESTful接口规范,可直接用于答辩演示或二次开发。 2025年做毕业设计,我选了“租车车辆管理系统”这个题,技术栈直接定成Java + SpringBoot3 + Vue.js3。说实话,这套组合在计算机专业毕设里已经是“标准答案”级别了,但真要从零把用户、车辆、订单、权限这一整套流程跑通,需要处理的问题远比想象中多。这篇文章是我整个项目的复盘,包括技术选型原因、后端核心模块怎么设计、前端Vue3工程怎么搭、部署阶段踩过的坑,以及最后答辩时最加分的几个点。如果你正在做同类型的系统,或者想在简历上写一个“能讲清楚”的项目,这篇内容可以直接拿来参考。
1. 项目整体设计与技术选型思路
1.1 为什么选择SpringBoot3 + Vue3这套组合
先说选型。2022年以后SpringBoot3正式发布,最直观的变化就是强制要求JDK17以上,并且把原来基于Java EE的javax.*包全部换成了jakarta.*。很多同学在初始化项目时还习惯找“javax.servlet”的代码,结果一跑就编译报错,这就是没有提前了解SpringBoot3带来的兼容性变动。我用SpringBoot3的原因很简单:它是当前最新的稳定主线,学校课程和招聘面试都在向这个版本靠拢,与其用旧版本做毕设,不如直接学新东西。
Vue3这边也一样。Vue3的组合式API比Vue2的选项式API更适合做中后台系统,配合Vite开发服务器,冷启动速度比Webpack时代的Vue CLI快很多。租车管理系统页面数量不多,但状态管理、路由守卫、接口拦截这些需求是标配,Vue3 + Pinia + Vue Router这套组合足够覆盖,而且社区资料已经非常完整,遇到问题很容易搜到解决方案。
我当时也考虑过用若依或Ruoyi-Vue这种脚手架。如果单从“快速出成果”的角度看,用脚手架确实省事,但毕设答辩老师最常问的就是“这个权限拦截是你自己写的吗”“这段逻辑能不能讲清楚”。如果直接用脚手架改改,原理一问三不知,风险很高。所以我选择从零搭建,核心代码都自己控制,这也是后面能顺利通过答辩的重要原因。
提示:如果你对SpringBoot3的底层还不熟,建议先花半天时间把“自动配置”“starter依赖”“条件注解”这几个概念过一遍,后面做任何模块都会顺手很多。
1.2 需求拆解:租车系统到底在管什么
很多同学拿到题目的第一反应是“不就是车辆增删改查吗”,但实际梳理下来,租车系统至少包含三个角色、两个端、一条完整业务链。
用户端核心流程是:注册登录 → 浏览车辆 → 选择车辆和租期 → 提交订单 → 模拟支付 → 到店取车 → 使用中 → 还车 → 结算费用。管理员端核心功能是:车辆信息管理、车辆状态维护、订单审核与调度、用户管理、经营数据统计。除了这两个端,系统还需要一个权限模型:普通用户只能操作自己的订单,管理员可以操作所有订单和车辆信息。
所以我把业务模块拆成了十个左右:用户认证、车辆管理、品牌管理、订单管理、支付模拟、还车结算、统计报表、操作日志、权限管理、公告管理。每个模块都不算复杂,但合在一起就构成了一个完整闭环。做毕设的时候最忌讳只做“单表增删改查”,因为那样体现不出系统性思维,答辩也很难有亮点。
数据流方面,我画过一条主线:用户下单时系统先判断车辆状态是否“可租”,然后创建订单,同时把车辆状态改成“已预订”;模拟支付成功后状态变成“待取车”,取车时改成“租用中”,还车后进入“待结算”,结算完成整个订单归档。车辆状态和订单状态是联动的,这一块是系统的核心逻辑,也是面试官最容易深挖的地方。
2. 后端核心模块实现要点
2.1 项目结构和数据库设计:先把地基打牢
后端我用的是标准的Controller-Service-Mapper三层结构,包名按功能划分:controller、service、mapper、entity、dto、vo、config、common、exception、util。实体类全部放在entity里,前端传参用dto接收,返回给前端的统一用vo封装,这样每个类职责清晰,不会出现一张表打天下的局面。
数据库我设计了六张核心表:用户表、车辆表、品牌表、订单表、操作日志表、公告表。车辆表包含车牌号、车辆名称、品牌ID、车型、座位数、排量、日租金、押金、车辆图片、状态、总行驶里程、创建时间等字段。订单表包含订单编号、用户ID、车辆ID、预计取车时间、预计还车时间、实际取车时间、实际还车时间、租车天数、订单金额、押金、状态、创建时间等字段。
这里有几个设计上容易忽略的点:
- 订单编号不要用自增ID,业务上最好生成一个可读的唯一单号,比如
yyyyMMddHHmmss + 用户ID后四位 + 随机数,方便后续排查问题。 - 金额字段用
DECIMAL(10, 2),不要用double,否则结算时会出现精度误差。 - 状态字段建议用
tinyint或varchar,不要用int裸存。我习惯用varchar存枚举名,比如AVAILABLE、RENTED,代码里可读性更强,排查问题时一眼就能看懂。 - 车辆表和订单表建立外键关系。虽然项目里不强制用物理外键,但逻辑上要保证订单关联的车辆存在,我通过
vehicleId做逻辑关联,并在Mapper查询时做left join把车辆信息带出来。
技术选型上我用了MyBatis-Plus,理由是它内置了分页插件和通用BaseMapper,简单查询不用写SQL,复杂统计再自己写XML。SpringBoot3要使用mybatis-plus-spring-boot3-starter这个依赖,不能用旧版的mybatis-plus-boot-starter,这是个很容易踩的坑。
2.2 用户认证与权限管理:Spring Security + JWT怎么落地
用户登录认证是几乎所有管理系统都绕不开的模块。我在项目里用的是Spring Security + JWT的方案。
为什么用JWT而不用Session?因为前后端完全分离,后端服务将来可能水平扩展,Session存内存里会有黏性问题;JWT是无状态的,服务器不保存登录状态,只要客户端拿着token,后端验签通过就认为是合法请求。这正好是面试里常说的“无状态认证”。
实现思路是:
- 用户注册时用
BCryptPasswordEncoder对密码做哈希处理,数据库里绝不存明文。 - 登录接口校验用户名和密码,成功后生成JWT,把用户ID、用户名、角色封装进token里,返回给前端。token有效期我设置的是24小时,毕设阶段够用。
- Spring Security配置类里关掉CSRF,放行登录、注册、车辆列表、车辆详情等公开接口,其余接口都需要认证。
- 写一个
JwtAuthenticationFilter继承OncePerRequestFilter,从请求头Authorization里取token,解析成功就把用户信息放进SecurityContextHolder,后续接口就能直接通过@AuthenticationPrincipal获取当前登录用户。 - 角色权限控制用
@PreAuthorize("hasRole('ADMIN')")注解,管理员接口只有管理员能访问,普通用户访问会返回403。
这个模块花了我最多时间,因为Spring Security的过滤器链机制对新手不太友好。一开始我把自定义过滤器放到了正确位置,结果导致登录接口也被拦截,返回401。后来查了官方文档,才发现要把过滤器加在UsernamePasswordAuthenticationFilter之前,并且要用http.addFilterBefore()而不是http.addFilter(),这两者的效果完全不同。
注意:JWT的密钥不要硬编码在代码里,应该写在
application.yml里,环境切换时用@Value读取。毕设虽然不要求安全等级多高,但这个习惯在真实项目中很重要。
2.3 车辆状态与订单状态流转:并发防超租的实现细节
租车系统最大的业务难点是“同一辆车不能被两个人同时租走”。如果只是简单的查询车辆 → 创建订单 → 修改车辆状态,在高并发场景下会出现两个用户同时查到“可租”,然后都下单成功的情况。
我采用的方案是数据库乐观锁。在车辆表里加一个version字段,下单时不直接update状态,而是执行条件更新:
UPDATE rent_vehicle SET status = 'BOOKED', version = version + 1 WHERE id = #{vehicleId} AND status = 'AVAILABLE' AND version = #{oldVersion}如果影响行数为0,说明车辆已经被别人抢先预订,直接抛出业务异常提示“手慢了,车辆已被预订”。同时订单状态的设计也严格区分:PENDING待支付、PAID已支付、PICKED_UP已取车、RETURNED已还车、FINISHED已完成、CANCELLED已取消。
每个状态之间都有明确的流转条件,比如订单只能从PENDING取消,支付成功后从PENDING进入PAID,取车时从PAID进入PICKED_UP,还车后从PICKED_UP进入RETURNED,管理端确认结算后才变成FINISHED。这样设计的好处是代码逻辑清晰,无论前端怎么调,后端都会做状态校验,非法请求直接被拦。
还车结算部分我按“日租金×天数+超时费”计算。租车天数按实际还车时间-实际取车时间算,不满一天按一天计,超时超过两小时额外收取半天租金。这些规则都是业务常识,答辩时能讲出来会很加分。我还额外加了“押金管理”的逻辑:下单时冻结押金,还车结算时如果无违章或损坏,押金原路退回(毕设里直接做成“模拟退回”,扣除订单金额后返回剩余押金)。这个点虽然简单,但让整个系统更接近真实场景。
3. 前端Vue3工程化实践
3.1 用Vite初始化项目与目录划分
前端我用Vite创建项目,命令是npm create vite@latest rent-car-frontend -- --template vue。如果你不熟悉TypeScript,直接用JavaScript模板就行,毕设讲清楚业务比堆类型重要。项目创建后,我又加了几个必备依赖:
npm install element-plus @element-plus/icons-vue npm install pinia npm install vue-router@4 npm install axios npm install echarts目录结构我做了清晰划分:
src/ ├── api/ # 接口请求封装 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── router/ # 路由配置 ├── stores/ # Pinia状态管理 ├── views/ # 页面 ├── utils/ # axios实例、格式化工具 ├── App.vue └── main.jsapi目录下按业务模块拆文件,比如user.js、vehicle.js、order.js,每个文件导出对应的请求方法。这样页面里不需要直接写axios.get,统一走封装好的接口,后续修改接口地址或统一处理逻辑都方便。
3.2 接口封装与登录态管理
Axios实例封装是前端工程化的关键一环。我创建了一个utils/request.js,配置baseURL: '/api',并在请求拦截器里自动携带token:
request.interceptors.request.use(config => { const user = JSON.parse(localStorage.getItem('userInfo') || '{}') if (user.token) { config.headers.Authorization = `Bearer ${user.token}` } return config })响应拦截器里统一处理后端返回的结构,比如后端约定{ code: 200, data: ..., message: ... },如果code不是200就弹出错误提示;如果是401就清空本地用户信息,跳到登录页。这样一来,业务代码里不需要重复写错误处理,逻辑干净很多。
登录态管理我用Pinia。定义了一个userStore,保存用户信息、token、角色和登录/登出方法,配合localStorage做持久化。路由守卫里判断:如果没有token且访问的是需要登录的页面,跳转到登录页并带上redirect参数,登录成功后自动回到之前想访问的页面。这个交互细节虽然不起眼,但在答辩演示时非常效果。
3.3 核心页面实现:车辆列表、下单流程、管理后台
车辆列表页是整个系统门面,我用了Element Plus的卡片布局,每张卡片显示车辆图片、名称、日租金、品牌、座位数,底部是“立即租车”按钮。搜索区域支持按品牌、按日租金区间、按车型筛选,这部分用了一个el-form绑定一个query对象,点击搜索时重新请求列表接口。
下单流程比较复杂。用户点击“立即租车”后,进入一个表单页,需要选择预计取车时间、预计还车时间,系统自动计算租车天数和总费用。这里有个坑:el-date-picker的值默认是数组格式,如果用value-format设置成"YYYY-MM-DD HH:mm:ss",提交给后端时会省去很多格式转换的麻烦。我一开始没设置,结果后端接收到的日期一直是null,折腾了半天才发现是前端传参格式问题。
管理后台我实现了车辆管理、订单管理、用户管理和统计报表。车辆管理和订单管理都用el-table加el-pagination,状态列用el-tag显示不同颜色,比如“可租”绿色、“租用中”橙色、“维修中”红色。统计报表使用ECharts,展示近七天订单量趋势和车辆利用率柱状图,后台管理首页还放了几个统计卡片:总车辆数、今日订单数、本月营收、待处理订单数。这些数据不是假的,都是通过后端统计接口实时查出来的,答辩时打开页面就能看到联动效果。
4. 环境配置与常见问题排查
4.1 Java环境变量和Maven配置,别再栽在第一步
很多同学代码写得没问题,结果项目启动不了,一看环境变量还没配好。SpringBoot3要求JDK17以上,我电脑里装了JDK17和JDK8两个版本,所以环境变量尤其重要。
Windows系统配置要点是:
- 新建
JAVA_HOME,值是JDK安装目录,比如C:\Program Files\Java\jdk-17。 - 在
Path变量里添加%JAVA_HOME%\bin。 - 新建
MAVEN_HOME,值是Maven安装目录。 Path里再添加%MAVEN_HOME%\bin。- 配置完成后,新开一个命令行窗口,执行
java -version和mvn -v验证,能看到17和Maven版本号就说明配置成功。
这里有个老生常谈的问题:明明配了JDK17,控制台java -version还是显示1.8。原因是系统Path里可能存在Oracle JDK或第三方软件写入的C:\Program Files (x86)\Common Files\Oracle\Java\javapath,它会把java指向老版本。解决办法是把这个路径变量删掉,或者把%JAVA_HOME%\bin在Path里的顺序提到最前面。
用IDEA开发时,还要检查项目SDK和Maven运行环境。File → Project Structure → Project里选择17,Settings → Build Tools → Maven → Runner里把JRE也改成17。否则即使IDEA本身没问题,Maven打包时也有可能会使用默认的JDK8,导致编译报错。
4.2 数据库初始化与常见启动报错
数据库我用的MySQL 8.0,初始化时新建一个rent_car数据库,编码选utf8mb4,排序规则utf8mb4_general_ci,然后执行项目的init.sql脚本创建表结构。SpringBoot配置文件中关键参数:
spring: datasource: url: jdbc:mysql://localhost:3306/rent_car?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver如果启动时报Access denied for user 'root'@'localhost',先确认密码是否正确;如果报Public Key Retrieval is not allowed,需要在URL后面加allowPublicKeyRetrieval=true。这两个是我见过最频繁的数据库连接报错。
构建过程中很容易遇到java: outofmemoryerror: insufficient memory。这个报错本质是Idea或Maven进程使用的堆内存不足,解决办法分两步:
- 在
Help → Edit Custom VM Options里把IDEA自身的最大堆调大,比如-Xmx2048m。 - 在Maven的
settings.xml或命令行中添加MAVEN_OPTS=-Xmx1024m -Xms512m,避免构建时内存不够。
如果你的项目依赖中包含不规范的包,也可能出现Maven构建时堆溢出。这时可以检查pom.xml是否引入了互相冲突的依赖,或使用mvn clean package -DskipTests重新构建。
4.3 前端Node环境与Vite代理配置
前端开发环境需要用Node.js 16以上的版本。安装完Node后,npm会自带,不必重复配置。但国内网络环境下,npm安装依赖经常卡住或下载超时,我建议配置一下npm镜像源:
npm config set registry https://registry.npmmirror.com这个操作不能多解释,懂的都懂,目的就是让下载依赖的速度回到正常水平。配置后重新执行npm install,基本一两分钟就能装完。
开发阶段最常遇到的是跨域问题。前端跑在http://localhost:5173,后端跑在http://localhost:8080,直接发ajax请求会被浏览器拦截。解决方案是在vite.config.js里配置代理:
server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } }这样前端请求/api/vehicle/list,Vite开发服务器会转发到http://localhost:8080/vehicle/list,后端接口不需要额外处理CORS。这个方案在开发阶段最简单,不需要引入拦截器或CorsFilter。
生产部署时,我把前端打包成静态文件,用Nginx托管,并配置了反向代理:
server { listen 80; server_name localhost; location / { root /home/rent-car/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这个配置解决了前端路由刷新后404的问题,同时也把/api开头的请求转发给后端,和开发环境的代理规则保持一致。
5. 项目复盘与答辩经验
5.1 功能测试和边界情况整理
项目写完只是第一步,能够经得起测试和推敲才是加分项。我在答辩前花了整整两天时间把整个系统的关键流程走了一遍,专门测那些“容易被忽略的边界情况”。
第一是重复下单。我在两个浏览器里同时登录不同账号,同时点击同一辆“可租”车辆的租车按钮,看是否只会有一个订单创建成功。因为加了乐观锁,第二个请求会收到“手慢了,车辆已被预订”的提示,数据库里也不会出现两条有效订单。
第二是权限越权。用普通用户账号去访问/admin/vehicle/list接口,预期返回403,页面也会被路由守卫拦截。为了测试接口层的权限,我直接修改前端请求地址,把普通用户的token带上,观察后端是否拦截。这个测试如果没做,答辩时被老师当场试一下就会很尴尬。
第三是订单状态非法流转。比如已经取消的订单,再调支付接口应该报错;已经还车的订单,再调取车接口应该报错。我在后端每个状态变更入口都做了校验,测试这些边界情况主要为了确认报错信息返回得是否友好,而不是让前端白屏。
5.2 毕设代码之外,最值得记录的几张“底牌”
答辩时老师不一定会一行一行看代码,但有亮点要主动展示。我准备了几张“底牌”:
- 乐观锁防超租设计,体现数据库并发意识。
- Spring Security + JWT的无状态认证,体现前后端分离架构的选型理由。
- 全局异常处理与统一返回体,体现友好错误响应。
- ECharts动态统计,体现数据可视化和SQL聚合能力。
- 订单状态机设计,体现业务建模能力。
这些点不需要很深奥,但每个都要能讲清楚“为什么这么设计”。比如乐观锁,我会从“两个人同时租同一辆车”的场景展开,讲清楚乐观锁和悲观锁的区别,然后说明我为什么选了乐观锁——因为车辆预订冲突概率不算特别高,用版本号控制比锁表更轻量。
5.3 后续还能怎么扩展
如果想把项目做得更完整,或者放在简历上更有竞争力,有几个方向值得延伸:
- 接入真实支付(支付宝沙箱)或微信支付,把模拟支付替换成真实回调,同时研究支付回调的幂等处理。
- 引入Redis缓存热门车辆列表、用户验证码,降低数据库压力。
- 把分页查询改成Elasticsearch全文搜索,支持按车型、品牌、价格区间做复杂筛选。
- 增加消息通知模块,在订单状态变化时给用户发送短信或邮件,这里可以聊到消息队列。
- 增加车牌识别和车辆定位能力,虽然毕设阶段用不上,但思路可以体现对行业趋势的关注。
我在实际项目里已经预留了部分扩展接口,比如订单状态变更的地方,我封装了一个OrderStatusManager,后面如果加通知或任务调度,直接从这里扩展就行。这比把状态逻辑到处写要干净得多,也方便后续维护。
最后再分享一个做毕设的小技巧:把所有关键设计决定和一个“为什么”记到项目README里。我每次调试完一个坑,都会在文档里补充一句当时的报错和解决方案。答辩前翻一遍,很多细节都记得很清楚,回答老师提问的时候明显更有底气。这个习惯后来用在工作里,帮我省了太多时间。
本文还有配套的精品资源,点击获取