ARTICLE DETAIL

资讯详情

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

SpringBoot3+Vue3实战:高校单车租赁管理系统设计与状态流转

SpringBoot3+Vue3实战:高校单车租赁管理系统设计与状态流转 如果你正在准备 Java 方向的项目实战、毕业设计或课程设计又不想把精力浪费在“只会写 CRUD、但串不起完整流程”的尴尬状态那“高校单车租赁管理系统”是一个非常合适的练手题目。它表面上是经典的管理系统实际上涉及用户角色、车辆状态流转、租赁订单生成与结算、管理员审核、前端联动等多层逻辑。这里我先把结论放在前面。这套系统真正值得自己动手做的原因只有一个它能让你用一套完整的前后端分离项目把 Java 面试中反复出现的 SpringBoot 自动装配、接口设计、事务处理、数据库索引、状态机约束等知识全部串起来。单独背八股文很难建立工程感但当你亲手把一辆车的状态从“可租”变成“租用中”再由“租用中”变成“已归还”的那一刻很多抽象概念会立刻变得具体。这篇文章会从选题价值、技术选型、数据库表设计、后端核心接口、Vue3 前端联调、项目启动验证、常见排错、工程化建议几个角度展开尽量做到“看一篇就能照着跑通”。代码示例以 SpringBoot3 Vue3 MySQL 为主整体思路也兼容其它管理系统类项目。1. 高校单车租赁管理系统到底在解决什么问题1.1 业务本质不是“卖车”而是“车辆的全生命周期管理”很多人在拿到这类题目时第一反应是“这不就是做几张表、写几个增删改查页面吗”。如果只是做一个简单的车辆登记系统确实可以这么理解但高校单车租赁的真实痛点并不在登记而在于车辆从投放校园到空闲停靠再到被学生租走、使用中、归还、异常报修、重新可租的完整生命周期。举个例子一辆单车被学生 A 扫码租走后它的状态必须是“租用中”。此时学生 B 在列表页看到这辆车就不能再发起租车请求。如果车辆被归还但管理员还未检查车况它应该进入“待检修”状态而不是立刻被下一位学生租走。如果不通过数据库状态字段控制这些环节会出现两类非常经典的业务问题超卖同一辆车被多个用户同时下单导致库存数据错乱。脏数据还车后不检查状态导致问题车继续流向市场。这类问题的根源不是“不会写 CRUD”而是“没有将业务动作抽象成状态变更”。所以在做数据库设计和后端接口时要优先把状态模型定清楚。1.2 为什么这类题目适合出现在简历和课程设计中从能力展示的角度看高校单车租赁管理系统非常契合初级 Java 开发岗位的考察重点。它不涉及复杂的分布式架构却能覆盖全栈开发的大部分基础能力。如果你能在项目文档中写清楚用户、车辆、订单、公告、报修等模块之间的关联关系并说明是如何通过事务保证租车还车数据一致性的会比简单罗列“熟练使用 SpringBoot”更有说服力。从学习路径的角度看初学者可以先在本地跑通单体版本再逐步引入 JWT 登录认证、统一异常处理、参数校验、MyBatis-Plus 条件构造、分页查询等进阶内容。也就是说这个项目不是死板的“毕设模板”而是一个可以从简单做到复杂的成长型项目。1.3 什么样的读者最适合看这篇文章这篇文章更适合下面几类读者正在准备 Java 后端岗位面试想做一套自己讲得清楚的实战项目。正在做毕业设计或课程设计题目就是单车租赁、共享车辆、校园资源预约类系统。刚开始接触 SpringBoot3 Vue3 前后端分离项目需要一条完整可落地的开发路径。如果你希望从零开始把环境、依赖、业务代码和联调完整跑通这篇文章可以作为项目起步的参考。2. 技术栈的关键选择SpringBoot3、Vue3、MySQL 分别承担什么2.1 Spring Boot 3 为什么必须配 Java 17从 Spring Boot 3.0 开始官方的一个重要变化是最低运行环境提升到 Java 17。这不仅影响本地 JDK 的安装版本也影响 IDE 和 Maven 的相关配置。很多人运行 Spring Boot 3 项目时直接启动失败最常见的原因就是本机 JDK 仍是 8。Java 17 属于 LTS长期支持版本是目前企业项目迁移和课程设计项目可以放心使用的主流版本。Spring Boot 3 内部基于 Jakarta EE 9 规范因此你在很多旧教程中看到的javax.servlet、javax.annotation包名在 Spring Boot 3 中已经统一变成jakarta.servlet、jakarta.annotation。这也能解释为什么网上一些 Spring Boot 2 代码直接粘贴到 Spring Boot 3 项目里会报包不存在。在后端职责上Spring Boot 3 主要提供接收前端 HTTP 请求通过 Controller 层完成参数接收和响应返回通过 Service 层封装“创建订单”“还车结算”等业务逻辑通过 Mapper 层与 MySQL 交互完成持久化通过全局异常处理统一返回 JSON 格式错误信息。2.2 Vue 3 的最大变化组合式 API 与工程化开发Vue 3 项目如果使用 Vite 作为构建工具会得到非常流畅的开发体验。相比 Vue 2 的 Options APIVue 3 的组合式 API 让相关逻辑可以聚合在一起。例如租车页面中加载车辆列表、处理租车按钮、处理加载状态这些逻辑可以放在同一个setup代码块里。前端需要做的核心工作是页面展示和接口请求。它在系统中不负责计算订单金额也不负责判断车辆是否可以被租借这些判断必须由后端最终确认。因此前端代码的重点是通过 Axios 请求后端 RESTful 接口根据返回数据渲染列表、弹窗和状态标签在发起关键操作前进行基础提示避免用户重复点击。关于 UI 组件库常见选择是 Element Plus。它提供表格、表单、日期选择器、消息提示等组件适合快速搭建管理后台和移动端适配页面。如果你不想引入整套组件库使用原生 HTML CSS 也能完成页面不过开发效率会低一些。2.3 MySQL 在系统中的核心位置MySQL 负责持久化存储用户、车辆、订单等数据。在单车租赁场景里数据库不只承担存储任务还承担事务一致性控制。例如用户还车时需要同时做两件事将订单状态从“租赁中”改成“已完成”将车辆状态从“租用中”改成“可租”或“待检修”。如果这两条 SQL 不是放在同一个事务中执行就可能在改完订单后、车辆状态还没更新时服务发生异常导致车辆一直显示“租用中”。这种异常在表数据量小的时候不容易出现但在并发环境下很可能造成数据错乱。因此在设计表时需要重点思考哪些字段可以唯一标识一条记录哪些字段需要建立索引哪些状态流转需要增加约束。MySQL 5.7 和 8.0 都能运行这类项目但推荐使用 MySQL 8.0 及以上版本因为对窗口函数、UTF-8 排序规则、JSON 类型的支持更好。2.4 前后端分离的接口约定前后端分离项目的关键不是“分成两个工程”而是“定义清楚接口规范”。高校单车租赁系统一般可以约定如下接口路径统一以/api/开头返回格式统一为{ code, message, data }登录后请求在请求头中携带Authorization字段后端使用 HTTP 状态码标识基本结果同时使用业务 code 表示业务错误类型。前端只需要认准这套格式就能把用户操作与后端服务联动起来。下面的代码是一个统一返回体的常用设计。// 文件路径src/main/java/com/example/bike/common/Result.java public class ResultT { private Integer code; private String message; private T data; public Result(Integer code, String message, T data) { this.code code; this.message message; this.data data; } public static T ResultT success(T data) { return new Result(200, 操作成功, data); } public static T ResultT success() { return new Result(200, 操作成功, null); } public static T ResultT error(Integer code, String message) { return new Result(code, message, null); } public Integer getCode() { return code; } public void setCode(Integer code) { this.code code; } public String getMessage() { return message; } public void setMessage(String message) { this.message message; } public T getData() { return data; } public void setData(T data) { this.data data; } }3. 数据库设计与核心状态机3.1 高校场景下有哪些角色与数据流高校单车租赁的用户角色简单但逻辑要层层拆开。常见的角色有三类普通学生用户可以浏览车辆、租车、还车、查看个人订单、上报故障。系统管理员负责维护车辆信息、审核车辆状态、查看所有订单、发布公告。系统本身的后台任务与数据流。如果项目还需要支持“教师”或“社团运营人员”可以扩展为同一用户表中的不同角色字段。最简单的方式是使用一个role字段区分比如1表示管理员2表示普通用户。这种设计比较直观后续如果要增加复杂的权限控制可以把角色字段升级为独立的角色表和用户-角色关联表。高校场景的业务数据流可以概括为四条主线用户从车辆列表中选择可用车辆创建订单车辆状态从“可租”变为“租用中”用户归还车辆订单状态更新管理员对归还车辆进行确认检修车辆重新变为“可租”。第二个主线是整个系统的核心。创建订单这个操作在后端并不是只插入一条订单记录那么简单必须同时完成车辆状态的更新。如果车辆状态更新失败订单就不能算创建成功。因此这个动作一定要放到同一个事务方法里。3.2 核心表结构设计下面用一个实用的最小模型来设计表结构。这个模型能支撑单校区场景的基本业务后续做扩展也很方便。用户表sys_user字段名类型说明idbigint主键自增usernamevarchar(50)用户名唯一passwordvarchar(100)加密后的密码real_namevarchar(50)姓名phonevarchar(20)手机号roletinyint1-管理员2-普通用户statustinyint0-禁用1-正常create_timedatetime创建时间车辆表bike字段名类型说明idbigint主键自增bike_novarchar(50)车辆编号唯一locationvarchar(100)停放位置statustinyint0-已下架1-可租2-租用中3-待检修qr_codevarchar(200)二维码内容或链接create_timedatetime上架时间订单表bike_order字段名类型说明idbigint主键自增order_novarchar(64)订单编号唯一user_idbigint用户IDbike_idbigint车辆IDstart_timedatetime租车开始时间end_timedatetime实际归还时间amountdecimal(10,2)订单金额statustinyint0-租赁中1-已完成2-已取消报修表repair_record字段名类型说明idbigint主键自增bike_idbigint车辆IDuser_idbigint上报用户IDdescriptionvarchar(500)问题描述statustinyint0-待处理1-已处理create_timedatetime上报时间这四张表已经可以支撑一个完整的单车租赁闭环。面试时可以重点讲清楚订单表和车辆表是一对多关系一辆车可以产生多条订单记录订单表中的user_id和bike_id是外键关联字段状态字段虽然简单但需要通过后端代码保证状态只按照预设方向流转。3.3 订单状态与车辆状态的变化关系很多初学者会把订单状态和车辆状态混在一起。实际上它们是两个独立概念只是业务上需要联动。订单状态描述的是“一次租赁交易进行到哪一步”车辆状态描述的是“这辆车当前是否空闲、是否可用”。当用户创建订单时订单状态从无到“租赁中”车辆状态从“可租”变为“租用中”。 当用户归还车辆时订单状态从“租赁中”变为“已完成”车辆状态从“租用中”变为“待检修”或“可租”。 当管理员检修完成后车辆状态才会从“待检修”变为“可租”。这套状态机看起来简单但在实际开发中很容易漏掉某一步。使用 Java 枚举来定义状态会比在 Service 层散落魔法数字更安全。// 文件路径src/main/java/com/example/bike/common/BikeStatus.java public enum BikeStatus { DISABLED(0, 已下架), AVAILABLE(1, 可租), RENTED(2, 租用中), MAINTENANCE(3, 待检修); private final Integer value; private final String description; BikeStatus(Integer value, String description) { this.value value; this.description description; } public Integer getValue() { return value; } public String getDescription() { return description; } }3.4 表设计时容易忽略的两个问题第一点是订单金额的存储类型。金额在 Java 中如果要使用浮点数double在计算时可能出现精度问题。在数据库表中金额字段建议使用decimal类型。在后端代码里金额计算可以使用BigDecimal避免浮点运算造成的误差。第二点是索引设计。订单表未来最大的查询场景是“根据当前用户查询他的历史订单”和“根据订单编号查询某笔订单详情”。因此可以在user_id上建立普通索引在order_no上建立唯一索引。车辆表的bike_no也是高频查询条件同样应该设置唯一索引。下面是一条规范的建表 SQL用来创建订单表。CREATE TABLE bike_order ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(64) NOT NULL COMMENT 订单编号, user_id bigint NOT NULL COMMENT 用户ID, bike_id bigint NOT NULL COMMENT 车辆ID, start_time datetime DEFAULT NULL COMMENT 租车开始时间, end_time datetime DEFAULT NULL COMMENT 实际归还时间, amount decimal(10, 2) DEFAULT 0.00 COMMENT 订单金额, status tinyint DEFAULT 0 COMMENT 0-租赁中 1-已完成 2-已取消, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_bike_id (bike_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT单车租赁订单表;4. 后端基础项目搭建与环境配置4.1 本地环境需要准备哪些内容由于项目使用的是 Spring Boot 3准备环境时请以这套组合为目标避免版本不匹配JDK建议安装 JDK 17 或更高版本。安装完成后建议在命令行执行java -version检查版本。Maven建议使用 3.6 版本。Maven 的主要作用是管理依赖和项目构建。IDEIntelliJ IDEA 社区版或专业版都可以。社区版可直接打开 Maven 项目不需要额外装插件。MySQL建议使用 8.0 版本。本地安装完成后需要确认 MySQL 服务已经启动并准备好一个可登录的账号。Node.js如果运行前端 Vue3 项目建议使用 Node 18 版本这样能与 Vite 配合得更加稳定。前端包管理工具使用 npm 即可pnpm 也可作为更高效率的替代方案。4.2 如何创建 Spring Boot 3 项目创建项目有两种方式。第一种是直接使用 Spring Initializr 网页生成模板访问官网后将项目类型选择为 Maven语言选择 JavaSpring Boot 版本选择 3.x然后在 Dependencies 中勾选 Spring Web、Validation、MySQL Driver生成后下载并用 IDEA 打开。第二种方式是在 IDEA 中新建 Spring Boot 项目。IDEA 内置的 Spring Initializr 工具会帮你完成同样的操作。如果你还没有使用熟练建议直接使用网页版生成减少因 IDEA 版本不同带来的界面差异。生成后的项目结构大致如下src/main/java/com/example/bike ├── common // 通用结果封装、异常处理、枚举 ├── config // Spring 配置类如跨域、WebMvc 配置 ├── controller // 控制器层 ├── entity // 数据库实体类 ├── mapper // MyBatis-Plus Mapper 接口 ├── service // 业务接口和实现类 └── BikeApplication.java这样的分包方式逻辑清晰面试时也能简单讲清楚每一层的作用。4.3 添加项目依赖在pom.xml中添加核心依赖时注意 Spring Boot 3 下 MyBatis-Plus 的 starter 名称已经变化不再是旧版教程中常见的mybatis-plus-boot-starter而是mybatis-plus-spring-boot3-starter。这是迁移到 Spring Boot 3 时最容易踩的依赖坑。下面是一份可用于项目开始阶段的pom.xml依赖配置示例。dependencies !-- Spring Boot Web 场景依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 参数校验 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency !-- SpringBoot3 兼容版 MyBatis-Plus -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.5/version /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Lombok 减少样板代码 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies如果你的项目要自行实现 JWT 登录认证可以再引入jjwt相关依赖。不过在教学版本中也可以先使用一个更简单的 Token 工具或在表示层验证后直接传用户 ID等项目跑通后再逐步替换成标准 JWT。4.4 核心配置文件后端与数据库的连接、MyBatis-Plus 的日志和实体映射都需要在application.yml中配置。下面是一个基础配置示例请根据你本机 MySQL 连接信息修改用户名和密码。# 文件路径src/main/resources/application.yml server: port: 8080 servlet: context-path: / spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/bike_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto配置的关键点在于map-underscore-to-camel-case。它负责把数据库中的create_time自动映射为 Java 实体中的createTime避免了手动编写大量resultMap。在线开发环境中MySQL 安装在 Docker 容器里的情况也非常常见只需要将url中的主机地址换成容器映射的宿主机地址即可。5. 后端核心接口的完整实现5.1 用户注册与登录登录接口是前后端分离项目的第一步。用户输入用户名和密码后后端完成校验并返回 Token 或用户基础信息。为提高安全性密码存库时不应该使用明文。下面是一个简化的密码加密思路使用 Spring Security Crypto 模块中提供的 BCrypt 工具。如果项目没有引入 Spring Security也可以单独引入spring-security-crypto。当然如果只是课程设计也可以先用 MD5 加盐了解流程但面试时应能说清楚 MD5 不适合直接存密码的原因。// 文件路径src/main/java/com/example/bike/controller/UserController.java RestController RequestMapping(/api/user) public class UserController { Resource private UserService userService; PostMapping(/login) public ResultMapString, Object login(RequestBody LoginDTO loginDTO) { MapString, Object data userService.login(loginDTO); return Result.success(data); } }登录服务实现时不要只验证用户名是否存在。还要判断用户状态是否被禁用以及在密码校验失败时给出统一提示避免日志中泄露太多账户存在性信息。5.2 创建租车订单创建订单是这个系统中最重要的接口。它涉及订单表插入、车辆状态更新两步操作因此必须使用事务。MyBatis-Plus 中可以直接在 Service 方法上使用TransactionalSpring 会帮助我们在方法执行异常时回滚事务。核心业务流程如下根据车辆 ID 查询车辆判断车辆状态是否为“可租”创建订单记录状态设置为“租赁中”修改车辆状态为“租用中”返回订单信息。为什么要先查询再更新因为数据库的并发控制需要配合唯一索引或乐观锁机制才能更稳妥地防止超卖。在课程设计阶段先通过Transactional保证同一次操作不会出现“插入订单成功但更新车辆失败”的情况已经是很大的进步。下面是用 MyBatis-Plus 的LambdaQueryWrapper实现查询条件的写法。// 文件路径src/main/java/com/example/bike/service/impl/OrderServiceImpl.java Override Transactional(rollbackFor Exception.class) public BikeOrder createOrder(Long userId, Long bikeId) { Bike bike bikeMapper.selectById(bikeId); if (bike null || !BikeStatus.AVAILABLE.getValue().equals(bike.getStatus())) { throw new BusinessException(该车辆当前不可租借); } String orderNo BK System.currentTimeMillis() RandomUtil.randomNumbers(4); BikeOrder order new BikeOrder(); order.setOrderNo(orderNo); order.setUserId(userId); order.setBikeId(bikeId); order.setStatus(OrderStatus.RENTING.getValue()); order.setStartTime(LocalDateTime.now()); orderMapper.insert(order); Bike updateBike new Bike(); updateBike.setId(bikeId); updateBike.setStatus(BikeStatus.RENTED.getValue()); bikeMapper.updateById(updateBike); return order; }这里的RandomUtil可使用 Hutool 工具包中的工具类。不引入工具包时可以使用UUID.randomUUID().toString().replace(-, ).substring(0, 8)生成随机串。5.3 还车结算还车接口看起来只是更新订单状态但真实业务中需要计算租赁时长和费用。计费规则如果在校内场景可以设置为“起步价 每小时费用”也可以设置为固定金额。为方便演示下面假设已经由一个BillingService负责计算金额。在还车时需要判断这个订单是否确实是当前用户发起的。如果用户登录后只传订单 ID不校验用户归属就会存在越权风险。这也是在前后端分离项目中容易被忽略的关键点。// 文件路径src/main/java/com/example/bike/service/impl/OrderServiceImpl.java Override Transactional(rollbackFor Exception.class) public BikeOrder returnBike(Long userId, Long orderId) { BikeOrder order orderMapper.selectById(orderId); if (order null || !OrderStatus.RENTING.getValue().equals(order.getStatus())) { throw new BusinessException(订单状态异常无法还车); } if (!order.getUserId().equals(userId)) { throw new BusinessException(无权操作该订单); } BigDecimal amount billingService.calculateAmount(order.getStartTime(), LocalDateTime.now()); BikeOrder update new BikeOrder(); update.setId(orderId); update.setStatus(OrderStatus.FINISHED.getValue()); update.setEndTime(LocalDateTime.now()); update.setAmount(amount); orderMapper.updateById(update); // 车辆归还后进入待检修由管理员确认后再变为可租 Bike bike new Bike(); bike.setId(order.getBikeId()); bike.setStatus(BikeStatus.MAINTENANCE.getValue()); bikeMapper.updateById(bike); return orderMapper.selectById(orderId); }这里设计了一个非常值得在文档中说明的细节还车之后车辆不直接变为“可租”而是进入“待检修”由管理员确认车况后再重新上架。这个细节能体现你对实际业务的理解而不只是纯技术实现。5.4 车辆管理与订单列表管理端模块通常需要车辆的分页列表、新增车辆、编辑车辆信息、上下架车辆以及查看全部订单列表。车辆分页查询可以直接使用 MyBatis-Plus 的Page功能。Controller 中接收分页参数的代码如下// 文件路径src/main/java/com/example/bike/controller/BikeController.java RestController RequestMapping(/api/bike) public class BikeController { Resource private BikeService bikeService; GetMapping(/page) public ResultIPageBike page(RequestParam(defaultValue 1) Integer current, RequestParam(defaultValue 10) Integer size, RequestParam(required false) String location) { PageBike page new Page(current, size); return Result.success(bikeService.pageBikes(page, location)); } }这里需要注意分页查询不是简单地取出全表数据再在前端分页。正确做法是通过 SQL 的LIMIT进行物理分页MyBatis-Plus 会基于分页插件自动处理。项目没有配置分页插件时Page可能无法生效所以不要遗漏 MyBatis-Plus 的配置类。// 文件路径src/main/java/com/example/bike/config/MybatisPlusConfig.java Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }5.5 关于事务边界与控制层不要写业务逻辑在编码时有个很容易犯的问题把业务判断写在 Controller 中。比如在 Controller 中先查询车辆状态再调用订单生成 Service。这种写法在单个接口中看似可行但一旦多个接口都要复用同一段租车逻辑就会产生重复代码。所以应坚持把业务动作放到 Service 层并通过Transactional控制事务边界。Controller 只负责接收请求参数、调用 Service、返回统一 Result。6. Vue3 前端项目实现与接口联调6.1 创建 Vue3 项目前端工程常见的创建方式是基于 Vite 的npm create vuelatest命令或npm create vitelatest命令。以 Vite 创建项目为例选择 Vue JavaScript 模板即可。由于高校单车租赁系统涉及管理后台如果对 TypeScript 还不太熟练可以先使用 JavaScript 减少学习成本。启动前端开发服务器后默认端口通常是 5173。实际与后端联调时需要避免端口或者跨域问题影响请求。6.2 API 请求封装前后端分离后前端不能像传统模板项目那样直接访问后端 Session。常规做法是每次请求都携带 Token。可以使用 Axios 统一在请求拦截器中注入Authorization头同时在响应拦截器中统一处理业务码错误和 401 未认证。下面是封装 Axios 的示例代码读者可以直接用于项目中的src/utils/request.js。// 文件路径src/utils/request.js import axios from axios import { ElMessage } from element-plus import { useUserStore } from /stores/user const request axios.create({ baseURL: http://localhost:8080/api, timeout: 10000 }) request.interceptors.request.use(config { const userStore useUserStore() if (userStore.token) { config.headers.Authorization userStore.token } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message || error)) } return res.data }, error { ElMessage.error(error.response?.data?.message || 网络异常) return Promise.reject(error) } ) export default request6.3 页面调用后端接口假设用户登录成功并进入“车辆列表”页面。这个页面需要加载所有状态为“可租”的车辆点击“租车”按钮后调用创建订单接口。Vue3 中可以直接在组件中使用组合式 API。一个简易的车辆列表部分逻辑如下。!-- 文件路径src/views/BikeList.vue -- script setup import { ref, onMounted } from vue import { ElMessage } from element-plus import request from /utils/request const bikeList ref([]) const loading ref(false) const loadBikes async () { loading.value true try { bikeList.value await request.get(/bike/list, { params: { status: 1 } }) } finally { loading.value false } } const rentBike async (bikeId) { await request.post(/order/create, { bikeId }) ElMessage.success(租车成功) loadBikes() } onMounted(() { loadBikes() }) /script这里需要理解一个关键点用户在页面上看到的“可租”状态只是一个展示结果真正防止多人同时操作同一辆车的安全屏障一定是后端。因此页面点击按钮后即使后端返回“该车辆已被租走”的错误也是在真实业务场景下正常的兜底情况。6.4 管理员页面与前端路由权限管理员的车辆管理页面、订单管理页面和普通用户页面最好通过前端路由分开。可以使用 Vue Router 的导航守卫在路由跳转时判断当前登录用户角色。但要注意前端路由守卫只能优化体验并不能保证接口安全。真正需要保护的数据接口后端也应该根据登录用户身份进行校验。7. 如何运行并验证整个项目7.1 启动后端服务启动后端前先保证 MySQL 服务运行并已执行 SQL 创建了数据库和数据表。然后在 IDEA 中启动BikeApplication如果启动成功控制台会出现 Spring Boot 的启动日志和 Tomcat 端口。建议先做一次简单的健康测试验证项目结构没有因为包名、路径问题出错。curl http://localhost:8080/api/user/login \ -H Content-Type: application/json \ -d {\username\:\admin\,\password\:\123456\}如果返回结果里有code: 200和用户数据说明后端接口和数据库已经连通。如果返回 500需要优先检查控制台打印的异常堆栈大多会是数据库连接失败或 SQL 语句问题。7.2 启动前端项目进入前端项目目录后先安装依赖再启动开发服务器。npm install npm run dev启动成功后浏览器访问命令行里输出的本地地址。如果页面请求后端时遇到跨域错误可以有两种解决方式。第一种是在后端实现跨域配置类。第二种是在前端 Vite 中配置开发代理把/api开头的请求代理到http://localhost:8080上。推荐使用 Vite 代理方式这样前端访问的同源地址可以避免浏览器跨域限制。下面是 Vite 配置文件中的一个节点// 文件路径vite.config.js export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })7.3 走一遍核心业务闭环当项目启动成功并且前后端连通后建议测试下面这条完整链路管理员登录后台新增一辆测试单车在车辆列表页面能看到这辆单车的状态为“可租”普通用户登录后点击租车订单状态变成“租赁中”车辆状态变成“租用中”在另一账号或另一个浏览器窗口刷新车辆列表该车辆不再显示为“可租”用户归还车辆订单状态变成“已完成”车辆状态变成“待检修”管理员执行检修完成操作车辆状态重新变成“可租”。这个闭环跑通以后项目核心功能就没有问题了。如果某一个环节的状态没有按预期变化应该先检查数据库中的这条数据再回头看 Service 代码里是否正确更新了相关状态字段。7.4 运行失败时的快速定位思路最简单的排错顺序是先看后端控制台再看数据库数据再看前端 Network 面板。不要一上来就怀疑是代码逻辑问题很多时候是后端没有启动、接口路径拼错、MySQL 密码不对、前端代理没生效。8. 常见问题与排查方法下面整理一张实际开发中最常见的问题对照表。问题现象可能原因排查方式解决方案后端启动报错提示 Java 版本过低本机 JDK 不是 17执行java -version安装 JDK17 并修改 IDEA Project Structure 中的 SDK启动报错无法连接 MySQLMySQL 服务未启动或密码不对使用 Navicat 或命令行连接测试启动 MySQL 服务修改application.yml中的连接配置依赖无法下载提示mybatis-plus-boot-starter找不到SpringBoot3 下用错了旧版 starter查看 Maven 仓库报错信息换成mybatis-plus-spring-boot3-starter页面请求接口提示跨域前端端口与后端端口不一致打开浏览器 Network 查看 CORS 错误添加后端跨域配置或使用 Vite proxy分页不生效接口返回全量数据没有配置 MyBatis-Plus 分页插件检查控制台 SQL 是否有 LIMIT添加 MybatisPlusInterceptor 配置前端调用接口后停留在登录页Token 过期或请求拦截器判断错误查看响应状态码和本地存储调整 Axios 响应拦截器的 401 处理逻辑创建订单后车辆状态没变化Service 方法未加事务或更新逻辑缺失查看数据库车辆状态在方法上添加Transactional并检查是否调用状态更新接口启动成功后页面 404前端路由与后端路径冲突或 context-path 配置问题检查 Vite 代理和目标路径确保接口前缀统一以/api开头这张表可以在项目进程里持续补充。对做课程设计和面试复盘而言自己整理这些问题比直接使用别人整理好的面试题更有效果因为每一条都是真实遇到过的坑。9. 工程化建议与扩展方向9.1 让项目更容易写清楚的部分如果想在项目文档或面试描述中讲得有条理可以从三个层面总结自己完成的工作第一业务层面完成了用户角色管理、车辆管理、租还车订单流转、报修流程和基础数据统计。第二工程层面采用前后端分离架构后端分 Controller、Service、Mapper 分层前端按页面组件拆分使用 Axios 统一管理请求接口返回统一 Result 结构。第三数据层面梳理了车辆状态机明确了订单表和车辆表之间的关联关系用事务确保租车和还车操作的数据一致性。这一套总结方法不只能用在单车管理系统上也可以迁移到会议室预约、实验室设备借用、图书漂流等类似场景的项目中。9.2 可以继续增强的方向项目跑通后如果想进一步提升难度可以按下面顺序补充功能点引入 JWT 无状态登录实现登录拦截和接口权限控制。为密码加盐或使用 BCrypt 加密不再存储明文密码。增加管理员审核租车资格比如限制某些违规用户无法租车。使用 Redis 缓存车辆状态和热区车辆降低数据库压力。引入定时任务自动取消超过一段时间未确认的异常订单。增加数据统计图表比如按日统计订单量、热门停放区域。其中 JWT 登录认证是现阶段 Java 后端面试中考察频率非常高的内容。你可以先不依赖框架手动实现一遍理解 Token 生成、解析、过期、拦截器注册等过程再对照成熟方案优化。9.3 安全提醒与生产环境边界这个项目如果只是本地学习或课程演示数据库密码放在配置文件里问题不大。但如果要部署到服务器供真实场景使用必须关注下面几个安全底线数据库连接使用独立账号最小化授权避免直接使用 root前端页面所有输入参数都不能直接信任后端必须做参数校验涉及删除操作时尽量先做备份或使用逻辑删除任何可能影响线上数据的操作先在测试环境验证配置文件中的密码、密钥不要提交到公开代码仓库。这几点不只是在提醒项目本身更是你在接触真实项目前需要建立的工程习惯。面试官更愿意听到“我知道登录认证只是第一道门槛真正的安全边界在后端接口与数据库权限设计”这类回答而不是只会说“所有页面都要登录才能打开”。9.4 后续学习路径建议如果你现在能把这个系统的租车、还车闭环讲清楚下一步建议直接脱离课程模板给自己设定一个新需求例如“为车辆增加预约功能”。预约会带来并发控制、库存扣减、超时释放等新挑战这时候你再去读 Redis、分布式锁或数据库乐观锁的相关资料理解会比第一次看时深得多。回到项目本身你需要反复确认的核心能力是把状态变化说清楚。无论是用户登录状态、订单状态还是车辆状态本质上都是一条数据的生命周期。能够设计好状态、控制好事务、在合适的层处理异常就已经超过很多只会做增删改查的初级项目了。
返回列表